mirror of
https://github.com/github/codeql-action.git
synced 2026-11-05 20:49:49 +00:00
Merge remote-tracking branch 'origin/main' into mbg/all-action-state
This commit is contained in:
2
.github/workflows/__autobuild-direct-tracing-with-working-dir.yml
generated
vendored
2
.github/workflows/__autobuild-direct-tracing-with-working-dir.yml
generated
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
|
||||
with:
|
||||
java-version: ${{ inputs.java-version || '17' }}
|
||||
distribution: temurin
|
||||
|
||||
2
.github/workflows/__build-mode-autobuild.yml
generated
vendored
2
.github/workflows/__build-mode-autobuild.yml
generated
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
|
||||
with:
|
||||
java-version: ${{ inputs.java-version || '17' }}
|
||||
distribution: temurin
|
||||
|
||||
36
.github/workflows/__bundle-from-toolcache.yml
generated
vendored
36
.github/workflows/__bundle-from-toolcache.yml
generated
vendored
@@ -46,6 +46,15 @@ jobs:
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- name: Install newer npm
|
||||
run: npm install -g npm@11.19.1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Prepare test
|
||||
id: prepare-test
|
||||
uses: ./.github/actions/prepare-test
|
||||
@@ -53,31 +62,20 @@ jobs:
|
||||
version: ${{ matrix.version }}
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
- name: Check toolcache contains CodeQL
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error(`CodeQL could not be found in the toolcache`);
|
||||
}
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts installed
|
||||
|
||||
- id: setup-codeql
|
||||
uses: ./../action/setup-codeql
|
||||
with:
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- name: Check CodeQL is installed within the toolcache
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
console.log(`Found CodeQL versions: ${allCodeqlVersions}`);
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error('CodeQL not found in toolcache');
|
||||
}
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts installed
|
||||
env:
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
|
||||
52
.github/workflows/__bundle-toolcache.yml
generated
vendored
52
.github/workflows/__bundle-toolcache.yml
generated
vendored
@@ -50,6 +50,15 @@ jobs:
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- name: Install newer npm
|
||||
run: npm install -g npm@11.19.1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Prepare test
|
||||
id: prepare-test
|
||||
uses: ./.github/actions/prepare-test
|
||||
@@ -58,47 +67,30 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Remove CodeQL from toolcache
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL');
|
||||
fs.rmdirSync(codeqlPath, { recursive: true });
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts clear
|
||||
|
||||
- name: Check toolcache does not contain CodeQL
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
if (allCodeqlVersions.length !== 0) {
|
||||
throw new Error(`CodeQL should not be found in the toolcache, but found ${allCodeqlVersions}`);
|
||||
}
|
||||
console.log('No versions of CodeQL found in the toolcache');
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts absent
|
||||
|
||||
- id: init
|
||||
uses: ./../action/init
|
||||
with:
|
||||
# Request multiple languages so this check uses the combined bundle.
|
||||
languages: javascript,python
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- uses: ./../action/analyze
|
||||
with:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
|
||||
- name: Check CodeQL is installed within the toolcache
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
console.log(`Found CodeQL versions: ${allCodeqlVersions}`);
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error('CodeQL not found in toolcache');
|
||||
}
|
||||
if (allCodeqlVersions.length > 1) {
|
||||
throw new Error('Multiple CodeQL versions found in toolcache');
|
||||
}
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts installed -n 1
|
||||
env:
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
|
||||
143
.github/workflows/__diagnostics-without-db.yml
generated
vendored
Normal file
143
.github/workflows/__diagnostics-without-db.yml
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
# Warning: This file is generated automatically, and should not be modified.
|
||||
# Instead, please modify the template in the pr-checks directory and run:
|
||||
# pr-checks/sync.sh
|
||||
# to regenerate this file.
|
||||
|
||||
name: PR Check - Diagnostic export without database
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GO111MODULE: auto
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- releases/v*
|
||||
pull_request: {}
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
workflow_call:
|
||||
inputs: {}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
||||
group: diagnostics-without-db-${{github.ref}}
|
||||
jobs:
|
||||
diagnostics-without-db:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: linked
|
||||
name: Diagnostic export without database
|
||||
if: github.triggering_actor != 'dependabot[bot]'
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: read
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
# We deliberately need to make this step fail before the database is initialised, but after
|
||||
# some diagnostic has been produced. The simplest way to do that right now is to:
|
||||
#
|
||||
# - Produce a diagnostic before the db is initialised by forcing the use of a nightly release
|
||||
# with the `CODEQL_ACTION_FORCE_NIGHTLY` feature flag.
|
||||
# - Cause `init` to abort because we are attempting to analyse Swift on a non-macOS runner.
|
||||
#
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- name: Install newer npm
|
||||
run: npm install -g npm@11.19.1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Prepare test
|
||||
id: prepare-test
|
||||
uses: ./.github/actions/prepare-test
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Initialise failing analysis
|
||||
id: init
|
||||
uses: ./../action/init
|
||||
continue-on-error: true
|
||||
env:
|
||||
CODEQL_ACTION_FORCE_NIGHTLY: true
|
||||
with:
|
||||
languages: swift
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- name: Analyze
|
||||
uses: ./analyze
|
||||
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
|
||||
# above, we manually disable it with an `if` condition. Having this step is required
|
||||
# for the init post-action because it checks the workflow definition for e.g. the
|
||||
# `category` input. Without this step, that part of the init post-action would fail.
|
||||
if: false
|
||||
with:
|
||||
category: '/test-codeql-version:${{ matrix.version }}'
|
||||
|
||||
- name: Check diagnostics appear on disk
|
||||
env:
|
||||
DIAGNOSTICS_DIR: '${{ runner.temp }}/codeql-action-temp-diagnostics/'
|
||||
run: |
|
||||
cd ../action
|
||||
npx tsx ./pr-checks/diagnostics-without-db.ts
|
||||
env:
|
||||
CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF: false
|
||||
CODEQL_ACTION_SKIP_SARIF_UPLOAD: true
|
||||
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
diagnostics-without-db-validation:
|
||||
if: github.triggering_actor != 'dependabot[bot]'
|
||||
needs:
|
||||
- diagnostics-without-db
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: read
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- name: Install newer npm
|
||||
run: npm install -g npm@11.19.1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Download workflow artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: test-mode-sarif-*
|
||||
path: sarif
|
||||
|
||||
- name: List artifact contents
|
||||
run: ls -laR ./sarif
|
||||
|
||||
- name: Check diagnostics appear in SARIF
|
||||
env:
|
||||
SARIF_PATH: "./sarif/codeql-failed-run.sarif"
|
||||
DIAGNOSTIC_ID: "codeql-action/forced-nightly-cli"
|
||||
DIAGNOSTIC_NAME: "A nightly release of CodeQL was used"
|
||||
EXPECTED_LOCATIONS: "0"
|
||||
EXPECTED_MESSAGE: |
|
||||
GitHub configured this analysis to use a nightly release of CodeQL to allow you to preview changes from an upcoming release.
|
||||
run: npx tsx ./pr-checks/check-diagnostics.ts
|
||||
env:
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
2
.github/workflows/__rubocop-multi-language.yml
generated
vendored
2
.github/workflows/__rubocop-multi-language.yml
generated
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
uses: ruby/setup-ruby@651ff0d62c5c79bed6d53ec518387aae20e72c1a # v1.329.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
4
.github/workflows/__submit-sarif-failure.yml
generated
vendored
4
.github/workflows/__submit-sarif-failure.yml
generated
vendored
@@ -71,7 +71,9 @@ jobs:
|
||||
run: exit 1
|
||||
- uses: ./analyze
|
||||
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
|
||||
# above, we manually disable it with an `if` condition.
|
||||
# above, we manually disable it with an `if` condition. Having this step is required
|
||||
# for the init post-action because it checks the workflow definition for e.g. the
|
||||
# `category` input. Without this step, that part of the init post-action would fail.
|
||||
if: false
|
||||
with:
|
||||
category: '/test-codeql-version:${{ matrix.version }}'
|
||||
|
||||
2
.github/workflows/post-release-mergeback.yml
vendored
2
.github/workflows/post-release-mergeback.yml
vendored
@@ -134,7 +134,7 @@ jobs:
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Generate token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.AUTOMATION_APP_ID }}
|
||||
|
||||
2
.github/workflows/rollback-release.yml
vendored
2
.github/workflows/rollback-release.yml
vendored
@@ -139,7 +139,7 @@ jobs:
|
||||
|
||||
- name: Generate token
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.AUTOMATION_APP_ID }}
|
||||
|
||||
2
.github/workflows/update-release-branch.yml
vendored
2
.github/workflows/update-release-branch.yml
vendored
@@ -95,7 +95,7 @@ jobs:
|
||||
pull-requests: write # needed to create pull request
|
||||
steps:
|
||||
- name: Generate token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.AUTOMATION_APP_ID }}
|
||||
|
||||
@@ -4,6 +4,10 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.39.1 - 21 Oct 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.27.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.3). [#4256](https://github.com/github/codeql-action/pull/4256)
|
||||
|
||||
## 4.39.0 - 15 Oct 2026
|
||||
|
||||
1156
lib/entry-points.js
generated
1156
lib/entry-points.js
generated
File diff suppressed because it is too large
Load Diff
145
package-lock.json
generated
145
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.39.1",
|
||||
"version": "4.39.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.39.1",
|
||||
"version": "4.39.2",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"pr-checks"
|
||||
@@ -36,7 +36,7 @@
|
||||
"node-forge": "^1.4.0",
|
||||
"semver": "^7.8.5",
|
||||
"undici": "^6.28.0",
|
||||
"uuid": "^14.0.2"
|
||||
"uuid": "^14.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
@@ -57,14 +57,14 @@
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^65.2.0",
|
||||
"eslint-plugin-jsdoc": "^65.3.4",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.13.0",
|
||||
"nock": "^14.0.17",
|
||||
"sinon": "^22.1.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.71.1"
|
||||
"typescript-eslint": "^8.71.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -2702,17 +2702,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz",
|
||||
"integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.2.tgz",
|
||||
"integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/type-utils": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.2",
|
||||
"@typescript-eslint/type-utils": "8.71.2",
|
||||
"@typescript-eslint/utils": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -2725,7 +2725,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.71.1",
|
||||
"@typescript-eslint/parser": "^8.71.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
@@ -2741,16 +2741,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz",
|
||||
"integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.2.tgz",
|
||||
"integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.2",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2784,14 +2784,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz",
|
||||
"integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.2.tgz",
|
||||
"integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.71.1",
|
||||
"@typescript-eslint/types": "^8.71.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.71.2",
|
||||
"@typescript-eslint/types": "^8.71.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2824,14 +2824,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz",
|
||||
"integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.2.tgz",
|
||||
"integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1"
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2842,9 +2842,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz",
|
||||
"integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.2.tgz",
|
||||
"integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2859,15 +2859,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz",
|
||||
"integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.2.tgz",
|
||||
"integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2",
|
||||
"@typescript-eslint/utils": "8.71.2",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -2902,9 +2902,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz",
|
||||
"integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.2.tgz",
|
||||
"integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2916,16 +2916,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz",
|
||||
"integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.2.tgz",
|
||||
"integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.71.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"@typescript-eslint/project-service": "8.71.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.71.2",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -3001,16 +3001,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz",
|
||||
"integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.2.tgz",
|
||||
"integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1"
|
||||
"@typescript-eslint/scope-manager": "8.71.2",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3025,13 +3025,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz",
|
||||
"integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.2.tgz",
|
||||
"integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5365,9 +5365,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc": {
|
||||
"version": "65.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.2.0.tgz",
|
||||
"integrity": "sha512-IMZaNwdDa5otUQYGWN1pzywZLsGKswitvFFNhcO8IOsBfaWpWIDXYuZh2sYhe9MNyKN9hz0YryOFaKdpbTP5Bw==",
|
||||
"version": "65.3.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.3.4.tgz",
|
||||
"integrity": "sha512-Z3/ro8sZPqnXKhyAzCNa3F7KCZ3js5xVx/q9BEvTjUGzN6OIQfdb+q3Sc9lJ3zCfyPnN0tI524ulZjYRxAwnAw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -9981,16 +9981,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.1.tgz",
|
||||
"integrity": "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.2.tgz",
|
||||
"integrity": "sha512-Rz4effbMy2g9wnvOJZG6SbVavX313Lap1vNEbk1RUochzickvzP8P4HB+bkNg5e50eVz1hQGjh9K1omI1xB4Bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.71.1",
|
||||
"@typescript-eslint/parser": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.71.2",
|
||||
"@typescript-eslint/parser": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2",
|
||||
"@typescript-eslint/utils": "8.71.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -10167,9 +10167,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "14.0.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz",
|
||||
"integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==",
|
||||
"version": "14.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz",
|
||||
"integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
@@ -10484,6 +10484,7 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/core": "^7.0.8",
|
||||
"@octokit/plugin-paginate-rest": ">=16.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.39.1",
|
||||
"version": "4.39.2",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -44,7 +44,7 @@
|
||||
"node-forge": "^1.4.0",
|
||||
"semver": "^7.8.5",
|
||||
"undici": "^6.28.0",
|
||||
"uuid": "^14.0.2"
|
||||
"uuid": "^14.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
@@ -65,14 +65,14 @@
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^65.2.0",
|
||||
"eslint-plugin-jsdoc": "^65.3.4",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.13.0",
|
||||
"nock": "^14.0.17",
|
||||
"sinon": "^22.1.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.71.1"
|
||||
"typescript-eslint": "^8.71.2"
|
||||
},
|
||||
"overrides": {
|
||||
"@actions/tool-cache": {
|
||||
|
||||
@@ -4,16 +4,32 @@ import * as core from "@actions/core";
|
||||
|
||||
import * as sarif from "../src/sarif";
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = "Plaintext message";
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`,
|
||||
);
|
||||
const DIAGNOSTIC_ID =
|
||||
process.env["DIAGNOSTIC_ID"] ?? "lang/diagnostics/example";
|
||||
const DIAGNOSTIC_NAME = process.env["DIAGNOSTIC_NAME"] ?? "Diagnostic name";
|
||||
const EXPECTED_MESSAGE = process.env["EXPECTED_MESSAGE"] ?? "Plaintext message";
|
||||
const EXPECTED_LOCATIONS = Number.parseInt(
|
||||
process.env["EXPECTED_LOCATIONS"] ?? "1",
|
||||
);
|
||||
|
||||
function checkStatusPageNotification(n: sarif.Notification) {
|
||||
const expectedMessage = EXPECTED_MESSAGE.trim();
|
||||
if (!n.message.text?.includes(expectedMessage)) {
|
||||
if (n.message.text !== undefined) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to contain the message '${expectedMessage}', but found '${n.message.text.replaceAll("\n", "\\n")}'.`,
|
||||
);
|
||||
} else {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to contain the message '${expectedMessage}', but found nothing.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
|
||||
const locationCount = n.locations?.length ?? 0;
|
||||
if (locationCount !== EXPECTED_LOCATIONS) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`,
|
||||
`Expected the status page diagnostic to have exactly ${EXPECTED_LOCATIONS} location(s), but found ${locationCount}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,8 +56,7 @@ if (toolExecutionNotifications === undefined) {
|
||||
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(
|
||||
(n) =>
|
||||
n.descriptor?.id === "lang/diagnostics/example" &&
|
||||
n.properties?.visibility?.statusPage,
|
||||
n.descriptor?.id === DIAGNOSTIC_ID && n.properties?.visibility?.statusPage,
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
@@ -61,9 +76,9 @@ if (notifications === undefined) {
|
||||
|
||||
const diagnosticNotification = notifications.filter(
|
||||
(n) =>
|
||||
n.id === "lang/diagnostics/example" &&
|
||||
n.name === "lang/diagnostics/example" &&
|
||||
n.fullDescription?.text === "Diagnostic name",
|
||||
n.id === DIAGNOSTIC_ID &&
|
||||
n.name === DIAGNOSTIC_ID &&
|
||||
n.fullDescription?.text === DIAGNOSTIC_NAME,
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
|
||||
@@ -2,30 +2,20 @@ name: "Bundle: From toolcache"
|
||||
description: "The CodeQL bundle should be cached within the toolcache"
|
||||
versions:
|
||||
- toolcache
|
||||
installNode: true
|
||||
steps:
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
- name: Check toolcache contains CodeQL
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error(`CodeQL could not be found in the toolcache`);
|
||||
}
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts installed
|
||||
|
||||
- id: setup-codeql
|
||||
uses: ./../action/setup-codeql
|
||||
with:
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- name: Check CodeQL is installed within the toolcache
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
console.log(`Found CodeQL versions: ${allCodeqlVersions}`);
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error('CodeQL not found in toolcache');
|
||||
}
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts installed
|
||||
|
||||
@@ -6,47 +6,31 @@ operatingSystems:
|
||||
- ubuntu
|
||||
- macos
|
||||
- windows
|
||||
installNode: true
|
||||
steps:
|
||||
- name: Remove CodeQL from toolcache
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL');
|
||||
fs.rmdirSync(codeqlPath, { recursive: true });
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts clear
|
||||
|
||||
- name: Check toolcache does not contain CodeQL
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
if (allCodeqlVersions.length !== 0) {
|
||||
throw new Error(`CodeQL should not be found in the toolcache, but found ${allCodeqlVersions}`);
|
||||
}
|
||||
console.log('No versions of CodeQL found in the toolcache');
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts absent
|
||||
|
||||
- id: init
|
||||
uses: ./../action/init
|
||||
with:
|
||||
# Request multiple languages so this check uses the combined bundle.
|
||||
languages: javascript,python
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- uses: ./../action/analyze
|
||||
with:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
|
||||
- name: Check CodeQL is installed within the toolcache
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const toolcache = require('@actions/tool-cache');
|
||||
const allCodeqlVersions = toolcache.findAllVersions('CodeQL');
|
||||
console.log(`Found CodeQL versions: ${allCodeqlVersions}`);
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error('CodeQL not found in toolcache');
|
||||
}
|
||||
if (allCodeqlVersions.length > 1) {
|
||||
throw new Error('Multiple CodeQL versions found in toolcache');
|
||||
}
|
||||
run: |
|
||||
cd ./../action
|
||||
npx tsx ./pr-checks/toolcache.ts installed -n 1
|
||||
|
||||
72
pr-checks/checks/diagnostics-without-db.yml
Normal file
72
pr-checks/checks/diagnostics-without-db.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
name: "Diagnostic export without database"
|
||||
description: "Tests that temporary diagnostics are correctly included in the failed SARIF."
|
||||
versions:
|
||||
# Overridden by the FF in the `init` step below.
|
||||
- linked
|
||||
installNode: true
|
||||
|
||||
env:
|
||||
# Allow the failed SARIF file to be generated.
|
||||
CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF: false
|
||||
# But don't actually upload it.
|
||||
CODEQL_ACTION_SKIP_SARIF_UPLOAD: true
|
||||
# Mark telemetry for this workflow so it can be treated separately.
|
||||
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
|
||||
|
||||
steps:
|
||||
# We deliberately need to make this step fail before the database is initialised, but after
|
||||
# some diagnostic has been produced. The simplest way to do that right now is to:
|
||||
#
|
||||
# - Produce a diagnostic before the db is initialised by forcing the use of a nightly release
|
||||
# with the `CODEQL_ACTION_FORCE_NIGHTLY` feature flag.
|
||||
# - Cause `init` to abort because we are attempting to analyse Swift on a non-macOS runner.
|
||||
#
|
||||
- name: Initialise failing analysis
|
||||
id: init
|
||||
uses: ./../action/init
|
||||
continue-on-error: true
|
||||
env:
|
||||
CODEQL_ACTION_FORCE_NIGHTLY: true
|
||||
with:
|
||||
languages: swift
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- name: Analyze
|
||||
uses: ./analyze
|
||||
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
|
||||
# above, we manually disable it with an `if` condition. Having this step is required
|
||||
# for the init post-action because it checks the workflow definition for e.g. the
|
||||
# `category` input. Without this step, that part of the init post-action would fail.
|
||||
if: false
|
||||
with:
|
||||
category: "/test-codeql-version:${{ matrix.version }}"
|
||||
|
||||
- name: Check diagnostics appear on disk
|
||||
env:
|
||||
DIAGNOSTICS_DIR: "${{ runner.temp }}/codeql-action-temp-diagnostics/"
|
||||
run: |
|
||||
cd ../action
|
||||
npx tsx ./pr-checks/diagnostics-without-db.ts
|
||||
validationJobs:
|
||||
diagnostics-without-db-validation:
|
||||
checkout: true
|
||||
installNode: true
|
||||
steps:
|
||||
- name: Download workflow artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: test-mode-sarif-*
|
||||
path: sarif
|
||||
|
||||
- name: List artifact contents
|
||||
run: ls -laR ./sarif
|
||||
|
||||
- name: Check diagnostics appear in SARIF
|
||||
env:
|
||||
SARIF_PATH: "./sarif/codeql-failed-run.sarif"
|
||||
DIAGNOSTIC_ID: "codeql-action/forced-nightly-cli"
|
||||
DIAGNOSTIC_NAME: "A nightly release of CodeQL was used"
|
||||
EXPECTED_LOCATIONS: "0"
|
||||
EXPECTED_MESSAGE: |
|
||||
GitHub configured this analysis to use a nightly release of CodeQL to allow you to preview changes from an upcoming release.
|
||||
run: npx tsx ./pr-checks/check-diagnostics.ts
|
||||
@@ -5,7 +5,7 @@ versions:
|
||||
- default
|
||||
steps:
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
uses: ruby/setup-ruby@651ff0d62c5c79bed6d53ec518387aae20e72c1a # v1.329.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -35,7 +35,9 @@ steps:
|
||||
run: exit 1
|
||||
- uses: ./analyze
|
||||
# In a real workflow, this step wouldn't run. Since we used `continue-on-error`
|
||||
# above, we manually disable it with an `if` condition.
|
||||
# above, we manually disable it with an `if` condition. Having this step is required
|
||||
# for the init post-action because it checks the workflow definition for e.g. the
|
||||
# `category` input. Without this step, that part of the init post-action would fail.
|
||||
if: false
|
||||
with:
|
||||
category: "/test-codeql-version:${{ matrix.version }}"
|
||||
|
||||
48
pr-checks/diagnostics-without-db.ts
Normal file
48
pr-checks/diagnostics-without-db.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import type * as diagnostics from "../src/diagnostics";
|
||||
|
||||
const DIAGNOSTIC_ID = "codeql-action/forced-nightly-cli";
|
||||
const diagnosticDirectory = process.env["DIAGNOSTICS_DIR"];
|
||||
|
||||
if (diagnosticDirectory === undefined) {
|
||||
throw new Error("DIAGNOSTICS_DIR is undefined.");
|
||||
}
|
||||
|
||||
const paths = fs
|
||||
.readdirSync(diagnosticDirectory, {
|
||||
recursive: true,
|
||||
encoding: "utf-8",
|
||||
})
|
||||
.filter((p) => p.endsWith(".json"));
|
||||
|
||||
core.info(`Found ${paths.length} paths in '${diagnosticDirectory}'.`);
|
||||
core.info(`Searching for a diagnostic with id '${DIAGNOSTIC_ID}'...`);
|
||||
|
||||
let found = false;
|
||||
for (const diagnosticPath of paths) {
|
||||
if (path.extname(diagnosticPath) !== ".json") {
|
||||
core.info(`Skipping ${diagnosticPath}.`);
|
||||
continue;
|
||||
}
|
||||
core.info(`Found ${diagnosticPath}.`);
|
||||
|
||||
const contents = JSON.parse(
|
||||
fs.readFileSync(path.resolve(diagnosticDirectory, diagnosticPath), "utf-8"),
|
||||
) as diagnostics.DiagnosticMessage;
|
||||
|
||||
if (contents.source.id === DIAGNOSTIC_ID) {
|
||||
core.info(
|
||||
`Found '${DIAGNOSTIC_ID}' in '${diagnosticPath}'. Skipping remaining files.`,
|
||||
);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new Error(`Didn't find '${DIAGNOSTIC_ID}' diagnostic.`);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/core": "^7.0.8",
|
||||
"@octokit/plugin-paginate-rest": ">=16.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
|
||||
|
||||
@@ -90,7 +90,7 @@ interface Specification extends JobSpecification {
|
||||
services?: any;
|
||||
|
||||
/** Additional jobs to run after the main PR check job. */
|
||||
validationJobs?: Record<string, JobSpecification>;
|
||||
validationJobs?: Record<string, ValidationJobSpecification>;
|
||||
|
||||
/** If set, this check is part of a named collection that gets its own caller workflow. */
|
||||
collection?: string;
|
||||
@@ -122,6 +122,10 @@ interface JobSpecification {
|
||||
installYq?: boolean;
|
||||
}
|
||||
|
||||
type ValidationJobSpecification = JobSpecification & {
|
||||
checkout?: boolean;
|
||||
};
|
||||
|
||||
/** Describes language/framework-specific steps and inputs. */
|
||||
interface LanguageSetup {
|
||||
specProperty: keyof JobSpecification;
|
||||
@@ -263,8 +267,8 @@ const languageSetups: LanguageSetups = {
|
||||
name: "Install Java",
|
||||
uses: pinnedUses(
|
||||
"actions/setup-java",
|
||||
"de7274f081f381c8f8158605e0321c36c376e2e6",
|
||||
"v6.0.1",
|
||||
"b843ebf9b913146b38da2eaa7aeddf76074ae1ad",
|
||||
"v6.1.0",
|
||||
),
|
||||
with: {
|
||||
"java-version": `\${{ inputs.java-version || '${defaultLanguageVersions.java}' }}`,
|
||||
@@ -462,6 +466,20 @@ function generateJobMatrix(
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `actions/checkout` step.
|
||||
*/
|
||||
function getCheckoutStep(): Step {
|
||||
return {
|
||||
name: "Check out repository",
|
||||
uses: pinnedUses(
|
||||
"actions/checkout",
|
||||
"3d3c42e5aac5ba805825da76410c181273ba90b1",
|
||||
"v7.0.1",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves setup steps and additional input definitions based on specific languages or frameworks
|
||||
* that are requested by the `checkSpecification`.
|
||||
@@ -531,14 +549,7 @@ function generateJob(
|
||||
|
||||
// Construct the workflow steps needed for this check.
|
||||
const steps: Step[] = [
|
||||
{
|
||||
name: "Check out repository",
|
||||
uses: pinnedUses(
|
||||
"actions/checkout",
|
||||
"3d3c42e5aac5ba805825da76410c181273ba90b1",
|
||||
"v7.0.1",
|
||||
),
|
||||
},
|
||||
getCheckoutStep(),
|
||||
...setupInfo.steps,
|
||||
{
|
||||
name: "Prepare test",
|
||||
@@ -609,13 +620,16 @@ function generateJob(
|
||||
/** Generates a validation job. */
|
||||
function generateValidationJob(
|
||||
specDocument: yaml.Document,
|
||||
jobSpecification: JobSpecification,
|
||||
jobSpecification: ValidationJobSpecification,
|
||||
checkName: string,
|
||||
name: string,
|
||||
) {
|
||||
// Determine which languages or frameworks have to be installed.
|
||||
const { inputs, steps } = getSetupSteps(jobSpecification);
|
||||
|
||||
// Construct a checkout step if requested.
|
||||
const checkoutStep = jobSpecification.checkout ? [getCheckoutStep()] : [];
|
||||
|
||||
// Extract the sequence of steps from the YAML document to persist as much formatting as possible.
|
||||
const specSteps = specDocument.getIn([
|
||||
"validationJobs",
|
||||
@@ -624,7 +638,7 @@ function generateValidationJob(
|
||||
]) as yaml.YAMLSeq;
|
||||
|
||||
// Add the generated steps in front of the ones from the specification.
|
||||
specSteps.items.unshift(...steps);
|
||||
specSteps.items.unshift(...checkoutStep, ...steps);
|
||||
|
||||
const validationJob: Record<string, any> = {
|
||||
name: jobSpecification.name,
|
||||
@@ -635,7 +649,7 @@ function generateValidationJob(
|
||||
"security-events": "read",
|
||||
},
|
||||
"timeout-minutes": 5,
|
||||
"runs-on": "ubuntu-slim",
|
||||
"runs-on": "ubuntu-latest",
|
||||
steps: specSteps,
|
||||
};
|
||||
|
||||
|
||||
94
pr-checks/toolcache.ts
Executable file
94
pr-checks/toolcache.ts
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/**
|
||||
* Provides utility commands for working with the Actions toolcache in PR checks.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import { parseArgs } from "node:util";
|
||||
import path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
|
||||
function removeCodeQLFromToolcache() {
|
||||
const toolcachePath = process.env["RUNNER_TOOL_CACHE"];
|
||||
|
||||
if (toolcachePath === undefined) {
|
||||
throw new Error("Expected RUNNER_TOOL_CACHE to be defined");
|
||||
}
|
||||
|
||||
const codeqlPath = path.join(toolcachePath, "CodeQL");
|
||||
fs.rmSync(codeqlPath, { recursive: true });
|
||||
|
||||
core.info(`Removed '${codeqlPath}' from the toolcache.`);
|
||||
}
|
||||
|
||||
function findCodeQL() {
|
||||
return toolcache.findAllVersions("CodeQL");
|
||||
}
|
||||
|
||||
function checkNoCodeQLInToolcache() {
|
||||
const allCodeqlVersions = findCodeQL();
|
||||
if (allCodeqlVersions.length !== 0) {
|
||||
throw new Error(
|
||||
`CodeQL should not be found in the toolcache, but found ${allCodeqlVersions}`,
|
||||
);
|
||||
}
|
||||
core.info("No versions of CodeQL found in the toolcache");
|
||||
}
|
||||
|
||||
function hasCodeQLInToolcache(expectedNumber?: string) {
|
||||
const expected =
|
||||
expectedNumber !== undefined
|
||||
? Number.parseInt(expectedNumber, 10)
|
||||
: undefined;
|
||||
|
||||
const allCodeqlVersions = findCodeQL();
|
||||
if (allCodeqlVersions.length === 0) {
|
||||
throw new Error("CodeQL not found in toolcache");
|
||||
}
|
||||
|
||||
core.info(`Found CodeQL versions: ${allCodeqlVersions.join(", ")}`);
|
||||
if (expected !== undefined && allCodeqlVersions.length !== expected) {
|
||||
throw new Error(
|
||||
`Expected ${expected} versions of CodeQL in the toolcache, but found ${allCodeqlVersions.length}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const { positionals, values } = parseArgs({
|
||||
options: {
|
||||
// The number of expected versions in the toolcache.
|
||||
"expected-version-count": { type: "string", short: "n" },
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (positionals.length < 1) {
|
||||
throw new Error("Command not specified.");
|
||||
}
|
||||
|
||||
const cmd = positionals[0];
|
||||
|
||||
switch (cmd.trim().toLowerCase()) {
|
||||
case "clear":
|
||||
removeCodeQLFromToolcache();
|
||||
return;
|
||||
case "absent":
|
||||
checkNoCodeQLInToolcache();
|
||||
return;
|
||||
case "installed":
|
||||
hasCodeQLInToolcache(values["expected-version-count"]);
|
||||
return;
|
||||
default:
|
||||
throw new Error(`Unknown command '${cmd}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (err) {
|
||||
core.setFailed(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "./actions-util";
|
||||
import { computeAutomationID } from "./api-client";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { RecordingLogger, setupTests } from "./testing-utils";
|
||||
import { initializeEnvironment, withTmpDir } from "./util";
|
||||
|
||||
@@ -238,7 +237,7 @@ test.serial("fixCodeQualityCategory", (t) => {
|
||||
GITHUB_EVENT_NAME: "dynamic",
|
||||
},
|
||||
() => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
// Categories that should get adjusted.
|
||||
t.is(fixCodeQualityCategory(logger, "/language:c#"), "/language:csharp");
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
supportedAnalysisKinds,
|
||||
} from "./analyses";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
createFeatures,
|
||||
RecordingLogger,
|
||||
@@ -65,7 +64,7 @@ test.serial(
|
||||
.withArgs("analysis-kinds")
|
||||
.returns("code-scanning,code-quality");
|
||||
const result = await getAnalysisKinds(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
features,
|
||||
true,
|
||||
);
|
||||
@@ -139,7 +138,7 @@ test.serial(
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("quality-queries").returns("code-quality");
|
||||
const result = await getAnalysisKinds(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
features,
|
||||
true,
|
||||
);
|
||||
@@ -155,7 +154,7 @@ test.serial(
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
|
||||
await t.throwsAsync(
|
||||
getAnalysisKinds(getRunnerLogger(true), features, true),
|
||||
getAnalysisKinds(new RecordingLogger(), features, true),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -181,7 +180,7 @@ for (let i = 0; i < analysisKinds.length; i++) {
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
const result = await getAnalysisKinds(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
features,
|
||||
true,
|
||||
);
|
||||
@@ -199,7 +198,7 @@ for (let i = 0; i < analysisKinds.length; i++) {
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
await t.throwsAsync(
|
||||
getAnalysisKinds(getRunnerLogger(true), features, true),
|
||||
getAnalysisKinds(new RecordingLogger(), features, true),
|
||||
{
|
||||
instanceOf: ConfigurationError,
|
||||
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
|
||||
|
||||
@@ -50,9 +50,8 @@ import {
|
||||
StatusReportBase,
|
||||
} from "./status-report";
|
||||
import {
|
||||
cleanupTrapCaches,
|
||||
TrapCacheCleanupStatusReport,
|
||||
uploadTrapCaches,
|
||||
warnIfTrapCacheCleanupEnvVarIsSet,
|
||||
} from "./trap-caching";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import { UploadResult } from "./upload-lib";
|
||||
@@ -86,7 +85,6 @@ async function sendStatusReport(
|
||||
trapCacheUploadTime: number | undefined,
|
||||
dbCreationTimings: DatabaseCreationTimings | undefined,
|
||||
didUploadTrapCaches: boolean,
|
||||
trapCacheCleanup: TrapCacheCleanupStatusReport | undefined,
|
||||
dependencyCacheResults: DependencyCacheUploadStatusReport | undefined,
|
||||
databaseUploadResults: DatabaseUploadResult[],
|
||||
logger: Logger,
|
||||
@@ -107,7 +105,6 @@ async function sendStatusReport(
|
||||
...statusReportBase,
|
||||
...(stats || {}),
|
||||
...(dbCreationTimings || {}),
|
||||
...(trapCacheCleanup || {}),
|
||||
dependency_caching_upload_results: dependencyCacheResults,
|
||||
database_upload_results: databaseUploadResults,
|
||||
};
|
||||
@@ -229,8 +226,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
| undefined = undefined;
|
||||
let runStats: QueriesStatusReport | undefined = undefined;
|
||||
let config: Config | undefined = undefined;
|
||||
let trapCacheCleanupTelemetry: TrapCacheCleanupStatusReport | undefined =
|
||||
undefined;
|
||||
let trapCacheUploadTime: number | undefined = undefined;
|
||||
let dbCreationTimings: DatabaseCreationTimings | undefined = undefined;
|
||||
let didUploadTrapCaches = false;
|
||||
@@ -278,6 +273,8 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
);
|
||||
}
|
||||
|
||||
warnIfTrapCacheCleanupEnvVarIsSet(action);
|
||||
|
||||
const apiDetails = getApiDetails();
|
||||
const outputDir = actionsUtil.getRequiredInput("output");
|
||||
action.env.export(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
|
||||
@@ -416,13 +413,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
didUploadTrapCaches = await uploadTrapCaches(codeql, config, logger);
|
||||
trapCacheUploadTime = performance.now() - trapCacheUploadStartTime;
|
||||
|
||||
// Clean up TRAP caches
|
||||
trapCacheCleanupTelemetry = await cleanupTrapCaches(
|
||||
config,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Store dependency cache(s) if dependency caching is enabled.
|
||||
if (shouldStoreCache(config.dependencyCachingEnabled)) {
|
||||
dependencyCacheResults = await uploadDependencyCaches(
|
||||
@@ -474,7 +464,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
trapCacheUploadTime,
|
||||
dbCreationTimings,
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
@@ -497,7 +486,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
trapCacheUploadTime,
|
||||
dbCreationTimings,
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
@@ -511,7 +499,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
trapCacheUploadTime,
|
||||
dbCreationTimings,
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
@@ -525,7 +512,6 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
trapCacheUploadTime,
|
||||
dbCreationTimings,
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
|
||||
@@ -17,12 +17,12 @@ import { defaultSuites } from "./config/db-config";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
setupTests,
|
||||
setupActionsVars,
|
||||
createFeatures,
|
||||
createTestConfig,
|
||||
RecordingLogger,
|
||||
callee,
|
||||
} from "./testing-utils";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
@@ -195,7 +195,7 @@ test.serial("status report fields", async (t) => {
|
||||
undefined,
|
||||
codeql,
|
||||
config,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
createFeatures([Feature.QaTelemetryEnabled]),
|
||||
);
|
||||
t.deepEqual(Object.keys(statusReport).sort(), [
|
||||
|
||||
@@ -401,21 +401,6 @@ export async function listActionsCaches(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an Actions cache item by its ID.
|
||||
*
|
||||
* See https://docs.github.com/en/rest/actions/cache#delete-a-github-actions-cache-for-a-repository-using-a-cache-id.
|
||||
*/
|
||||
export async function deleteActionsCache(id: number) {
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
|
||||
await getApiClient().rest.actions.deleteActionsCacheById({
|
||||
owner: repositoryNwo.owner,
|
||||
repo: repositoryNwo.repo,
|
||||
cache_id: id,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all custom repository properties.
|
||||
*
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
scanArtifactsForTokens,
|
||||
TokenType,
|
||||
} from "./artifact-scanner";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getRecordingLogger,
|
||||
LoggedMessage,
|
||||
makeTestToken,
|
||||
RecordingLogger,
|
||||
} from "./testing-utils";
|
||||
|
||||
test("makeTestToken", (t) => {
|
||||
@@ -121,7 +121,7 @@ for (const { type, value, checkPattern } of testTokens) {
|
||||
}
|
||||
|
||||
test("scanArtifactsForTokens handles files without tokens", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-"));
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,15 +3,14 @@ import path from "path";
|
||||
|
||||
import test from "ava";
|
||||
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import { setupTests } from "../testing-utils";
|
||||
import { RecordingLogger, setupTests } from "../testing-utils";
|
||||
import * as util from "../util";
|
||||
|
||||
import { getCachedCodeQlVersion } from "./output-cache";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
test.serial(
|
||||
"getCachedCodeQlVersion reuses a version persisted by an earlier step",
|
||||
|
||||
@@ -22,7 +22,6 @@ import type { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { ToolsSource } from "./setup-codeql";
|
||||
import {
|
||||
setupTests,
|
||||
@@ -34,6 +33,8 @@ import {
|
||||
makeVersionInfo,
|
||||
createTestConfig,
|
||||
makeMacro,
|
||||
getCodeQLForTesting,
|
||||
RecordingLogger,
|
||||
} from "./testing-utils";
|
||||
import { ToolsDownloadStatusReport } from "./tools-download";
|
||||
import * as util from "./util";
|
||||
@@ -102,7 +103,7 @@ async function installIntoToolcache({
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
@@ -140,7 +141,7 @@ function mockApiDetails(apiDetails: Required<GitHubApiDetails>) {
|
||||
}
|
||||
|
||||
async function stubCodeql(): Promise<codeql.CodeQL> {
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
const codeqlObject = await getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6"));
|
||||
sinon
|
||||
.stub(codeqlObject, "isTracedLanguage")
|
||||
@@ -176,7 +177,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -212,7 +213,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -252,7 +253,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
t.assert(toolcache.find("CodeQL", "0.0.0-20200610"));
|
||||
@@ -362,7 +363,7 @@ for (const {
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
t.assert(toolcache.find("CodeQL", expectedToolcacheVersion));
|
||||
@@ -405,7 +406,7 @@ for (const toolcacheVersion of [
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
t.is(
|
||||
@@ -450,7 +451,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200601");
|
||||
@@ -497,7 +498,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
@@ -537,7 +538,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
@@ -579,7 +580,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -664,7 +665,7 @@ const injectedConfigMacro = makeMacro({
|
||||
tempDir,
|
||||
};
|
||||
thisStubConfig.computedConfig = generateCodeScanningConfig(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
thisStubConfig.originalUserInput,
|
||||
augmentationProperties,
|
||||
);
|
||||
@@ -1039,7 +1040,7 @@ test.serial("runTool summarizes autobuilder errors", async (t) => {
|
||||
[2019-09-18 12:00:01] [autobuild] A non-error message
|
||||
`;
|
||||
stubToolRunnerConstructor(1, stderr);
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
const codeqlObject = await getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6"));
|
||||
sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor");
|
||||
// io throws because of the test CodeQL object.
|
||||
@@ -1095,7 +1096,7 @@ test.serial("runTool recognizes fatal internal errors", async (t) => {
|
||||
com.semmle.util.exception.CatastrophicError: An error occurred while evaluating ControlFlowGraph::ControlFlow::Root.isRootOf/1#dispred#f610e6ed/2@86282cc8
|
||||
Severe disk cache trouble (corruption or out of space) at /home/runner/work/_temp/codeql_databases/go/db-go/default/cache/pages/28/33.pack: Failed to write item to disk`;
|
||||
stubToolRunnerConstructor(1, stderr);
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
const codeqlObject = await getCodeQLForTesting();
|
||||
sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6"));
|
||||
sinon.stub(codeqlObject, "resolveExtractor").resolves("/path/to/extractor");
|
||||
// io throws because of the test CodeQL object.
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from "./feature-flags";
|
||||
import { isAnalyzingDefaultBranch } from "./git-utils";
|
||||
import { Language } from "./languages";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import type { Logger } from "./logging";
|
||||
import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from "./overlay";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import * as setupCodeql from "./setup-codeql";
|
||||
@@ -199,6 +199,7 @@ export interface CodeQL {
|
||||
sarifFile: string,
|
||||
automationDetailsId: string | undefined,
|
||||
config: ActionConfig<"tempDir">,
|
||||
diagnosticDir: string | undefined,
|
||||
): Promise<void>;
|
||||
/** Get the location of an extractor for the specified language. */
|
||||
resolveExtractor(language: Language): Promise<string>;
|
||||
@@ -486,18 +487,6 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a real, newly created CodeQL instance for testing. The instance refers to
|
||||
* a non-existent placeholder codeql command, so tests that use this function
|
||||
* should also stub the toolrunner.ToolRunner constructor.
|
||||
*/
|
||||
export async function getCodeQLForTesting(
|
||||
cmd = "codeql-for-testing",
|
||||
logger: Logger = getRunnerLogger(true),
|
||||
): Promise<CodeQL> {
|
||||
return getCodeQLForCmd(logger, cmd, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a CodeQL object for CodeQL CLI access.
|
||||
*
|
||||
@@ -506,7 +495,7 @@ export async function getCodeQLForTesting(
|
||||
* version requirement. Must be set to true outside tests.
|
||||
* @returns A new CodeQL object
|
||||
*/
|
||||
async function getCodeQLForCmd(
|
||||
export async function getCodeQLForCmd(
|
||||
logger: Logger,
|
||||
cmd: string,
|
||||
checkVersion: boolean,
|
||||
@@ -915,15 +904,43 @@ async function getCodeQLForCmd(
|
||||
sarifFile: string,
|
||||
automationDetailsId: string | undefined,
|
||||
config: ActionConfig<"tempDir">,
|
||||
diagnosticDir: string | undefined,
|
||||
): Promise<void> {
|
||||
// We may be exporting diagnostics for a failed SARIF, in which case the configuration
|
||||
// file may not have been written to disk and there may not be a database. The CLI
|
||||
// will produce a SARIF for diagnostics from `diagnosticDir` without it. Therefore,
|
||||
// we check whether `configPath` exists and only provide it to the CLI if it does.
|
||||
// Although not needed, the CLI will produce an error if it
|
||||
// is provided with an argument to `--sarif-codescanning-config` that does not exist.
|
||||
let configPath: string | undefined =
|
||||
getGeneratedCodeScanningConfigPath(config);
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
// Warn if there is no configuration file, even though `config` claims that `init`
|
||||
// was successful. This shouldn't be happening.
|
||||
if (config.successfullyInitialised) {
|
||||
logger.warning(
|
||||
[
|
||||
`Expected a configuration file at '${configPath}', which does not exist.`,
|
||||
"Continuing with diagnostics export anyway, without a configuration file.",
|
||||
].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
configPath = undefined;
|
||||
}
|
||||
|
||||
const args = [
|
||||
"diagnostics",
|
||||
"export",
|
||||
"--format=sarif-latest",
|
||||
`--output=${sarifFile}`,
|
||||
`--sarif-codescanning-config=${getGeneratedCodeScanningConfigPath(
|
||||
config,
|
||||
)}`,
|
||||
...(configPath !== undefined
|
||||
? [`--sarif-codescanning-config=${configPath}`]
|
||||
: []),
|
||||
...(diagnosticDir !== undefined
|
||||
? [`--diagnostic-dir=${diagnosticDir}`]
|
||||
: []),
|
||||
...getExtraOptionsFromEnv(["diagnostics", "export"]),
|
||||
];
|
||||
if (automationDetailsId !== undefined) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import { RepositoryProperties } from "./feature-flags/properties";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { GitVersionInfo } from "./git-utils";
|
||||
import { BuiltInLanguage, Language } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { CODEQL_OVERLAY_MINIMUM_VERSION } from "./overlay";
|
||||
import * as overlayDiagnostics from "./overlay/diagnostics";
|
||||
import { OverlayDisabledReason } from "./overlay/diagnostics";
|
||||
@@ -42,6 +41,7 @@ import {
|
||||
callee,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
AssertableTarget,
|
||||
RecordingLogger,
|
||||
} from "./testing-utils";
|
||||
import {
|
||||
GitHubVariant,
|
||||
@@ -102,7 +102,7 @@ function createTestInitConfigInputs(
|
||||
features: createFeatures([]),
|
||||
repositoryProperties: {},
|
||||
enableFileCoverageInformation: true,
|
||||
logger: getRunnerLogger(true),
|
||||
logger: new RecordingLogger(),
|
||||
} satisfies configUtils.InitConfigInputs,
|
||||
overrides,
|
||||
);
|
||||
@@ -150,7 +150,7 @@ function mockListLanguages(languages: string[]) {
|
||||
|
||||
test.serial("load empty config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const languages = "javascript,python";
|
||||
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
@@ -196,7 +196,7 @@ test.serial("load empty config", async (t) => {
|
||||
|
||||
test.serial("load code quality config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const languages = "actions";
|
||||
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
@@ -252,7 +252,7 @@ test.serial(
|
||||
"initActionState doesn't throw if there are queries configured in the repository properties",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const languages = "javascript";
|
||||
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
@@ -315,7 +315,7 @@ test.serial(
|
||||
|
||||
test.serial("loading a saved config produces the same config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async resolveLanguages() {
|
||||
@@ -363,7 +363,7 @@ test.serial("loading a saved config produces the same config", async (t) => {
|
||||
|
||||
test.serial("loading config with version mismatch throws", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async resolveLanguages() {
|
||||
@@ -788,12 +788,12 @@ test.serial("Unknown languages", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
const mockLogger = getRunnerLogger(true);
|
||||
const mockLogger = new RecordingLogger();
|
||||
|
||||
test.serial("no generateRegistries when registries is undefined", async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const registriesInput = undefined;
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const { registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
|
||||
|
||||
@@ -814,7 +814,7 @@ test.serial(
|
||||
token: "not-a-token",
|
||||
},
|
||||
]);
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const { registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
|
||||
|
||||
@@ -2225,7 +2225,7 @@ test("applyIncrementalAnalysisSettings: no-op when mode is not Overlay and diff
|
||||
const config = createTestConfig({});
|
||||
config.overlayDatabaseMode = OverlayDatabaseMode.None;
|
||||
const codeql = createStubCodeQL({});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
await configUtils.applyIncrementalAnalysisSettings(
|
||||
config,
|
||||
@@ -2243,7 +2243,7 @@ test("applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions w
|
||||
overlayDatabaseMode: OverlayDatabaseMode.Overlay,
|
||||
});
|
||||
const codeql = createStubCodeQL({});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
await configUtils.applyIncrementalAnalysisSettings(
|
||||
config,
|
||||
@@ -2266,7 +2266,7 @@ test.serial(
|
||||
});
|
||||
config.useOverlayDatabaseCaching = true;
|
||||
const codeql = createStubCodeQL({});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const addDiagnosticsStub = sinon
|
||||
.stub(overlayDiagnostics, "addOverlayDisablementDiagnostics")
|
||||
.resolves();
|
||||
@@ -2298,7 +2298,7 @@ test.serial(
|
||||
config.useOverlayDatabaseCaching = false;
|
||||
config.overlayModeSetExplicitly = true;
|
||||
const codeql = createStubCodeQL({});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const addDiagnosticsStub = sinon
|
||||
.stub(overlayDiagnostics, "addOverlayDisablementDiagnostics")
|
||||
.resolves();
|
||||
@@ -2321,7 +2321,7 @@ test("applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only r
|
||||
const config = createTestConfig({});
|
||||
config.overlayDatabaseMode = OverlayDatabaseMode.None;
|
||||
const codeql = createStubCodeQL({});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
await configUtils.applyIncrementalAnalysisSettings(
|
||||
config,
|
||||
@@ -2535,7 +2535,7 @@ test("determineUserConfig - merges configs if FF is enabled in Default Setup", a
|
||||
// also check whether loading the configuration from disk that was written
|
||||
// by `determineUserConfig` matches our expectations.
|
||||
const loadedFromDisk = configUtils.getLocalConfig(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
expectedConfigPath,
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -465,6 +465,8 @@ export async function initActionState(
|
||||
overlayModeSetExplicitly: false,
|
||||
repositoryProperties,
|
||||
enableFileCoverageInformation,
|
||||
// We don't set this to `true` until `init` is successful.
|
||||
successfullyInitialised: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,9 @@ export interface Config {
|
||||
* Whether to enable file coverage information.
|
||||
*/
|
||||
enableFileCoverageInformation: boolean;
|
||||
|
||||
/** Whether the analysis was successfully initialised using the configuration. */
|
||||
successfullyInitialised: boolean;
|
||||
}
|
||||
|
||||
/** Properties available in `Config`. */
|
||||
|
||||
@@ -2,15 +2,15 @@ import test, { ExecutionContext } from "ava";
|
||||
|
||||
import { RepositoryProperties } from "../feature-flags/properties";
|
||||
import { BuiltInLanguage, Language } from "../languages";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getRecordingLogger,
|
||||
LoggedMessage,
|
||||
makeMacro,
|
||||
RecordingLogger,
|
||||
setupActionsVars,
|
||||
} from "../testing-utils";
|
||||
import { ConfigurationError, prettyPrintPack } from "../util";
|
||||
import { ConfigurationError, prettyPrintPack, withTmpDir } from "../util";
|
||||
|
||||
import * as dbConfig from "./db-config";
|
||||
|
||||
@@ -384,7 +384,7 @@ calculateAugmentationErrorMacro(
|
||||
|
||||
test("parseUserConfig - successfully parses valid YAML", (t) => {
|
||||
const result = dbConfig.parseUserConfig(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
"test",
|
||||
`
|
||||
paths-ignore:
|
||||
@@ -410,7 +410,7 @@ test("parseUserConfig - throws a ConfigurationError if the file is not valid YAM
|
||||
t.throws(
|
||||
() =>
|
||||
dbConfig.parseUserConfig(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
"test",
|
||||
`
|
||||
paths-ignore:
|
||||
@@ -562,74 +562,89 @@ test("mergeDefaultSetupAndUserConfigs - keeps other properties from user-supplie
|
||||
t.deepEqual(result, configFile);
|
||||
});
|
||||
|
||||
test("mergeDefaultSetupAndUserConfigs - ignores, but warns about, unknown keys from Default Setup", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const configFile: dbConfig.UserConfig = {
|
||||
"query-filters": [{ exclude: { a: "b" } }],
|
||||
"paths-ignore": ["path"],
|
||||
};
|
||||
test.serial(
|
||||
"mergeDefaultSetupAndUserConfigs - ignores, but warns about, unknown keys from Default Setup",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const configFile: dbConfig.UserConfig = {
|
||||
"query-filters": [{ exclude: { a: "b" } }],
|
||||
"paths-ignore": ["path"],
|
||||
};
|
||||
|
||||
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
|
||||
logger,
|
||||
{
|
||||
"default-setup": {
|
||||
borg: [],
|
||||
org: {
|
||||
unknown: "foo",
|
||||
"model-packs": [],
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
|
||||
logger,
|
||||
{
|
||||
"default-setup": {
|
||||
borg: [],
|
||||
org: {
|
||||
unknown: "foo",
|
||||
"model-packs": [],
|
||||
},
|
||||
} as unknown as dbConfig.DefaultSetupConfig,
|
||||
"paths-ignore": ["other-path"],
|
||||
},
|
||||
} as unknown as dbConfig.DefaultSetupConfig,
|
||||
"paths-ignore": ["other-path"],
|
||||
},
|
||||
configFile,
|
||||
);
|
||||
configFile,
|
||||
);
|
||||
|
||||
t.deepEqual(result, {
|
||||
...configFile,
|
||||
"default-setup": { org: { "model-packs": [] } },
|
||||
});
|
||||
t.deepEqual(result, {
|
||||
...configFile,
|
||||
"default-setup": { org: { "model-packs": [] } },
|
||||
});
|
||||
|
||||
const expectedUnrecognisedKeys = [
|
||||
".default-setup.org.unknown",
|
||||
".default-setup.borg",
|
||||
".paths-ignore",
|
||||
].join(", ");
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Unrecognised keys in Default Setup configuration: ${expectedUnrecognisedKeys}`,
|
||||
]);
|
||||
});
|
||||
const expectedUnrecognisedKeys = [
|
||||
".default-setup.org.unknown",
|
||||
".default-setup.borg",
|
||||
".paths-ignore",
|
||||
].join(", ");
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Unrecognised keys in Default Setup configuration: ${expectedUnrecognisedKeys}`,
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("mergeDefaultSetupAndUserConfigs - warns about invalid keys from Default Setup", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const configFile: dbConfig.UserConfig = {};
|
||||
test.serial(
|
||||
"mergeDefaultSetupAndUserConfigs - warns about invalid keys from Default Setup",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const configFile: dbConfig.UserConfig = {};
|
||||
|
||||
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
|
||||
logger,
|
||||
{
|
||||
"default-setup": {
|
||||
org: {
|
||||
"model-packs": [123],
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
const result = dbConfig.mergeDefaultSetupAndUserConfigs(
|
||||
logger,
|
||||
{
|
||||
"default-setup": {
|
||||
org: {
|
||||
"model-packs": [123],
|
||||
},
|
||||
} as unknown as dbConfig.DefaultSetupConfig,
|
||||
},
|
||||
} as unknown as dbConfig.DefaultSetupConfig,
|
||||
},
|
||||
configFile,
|
||||
);
|
||||
configFile,
|
||||
);
|
||||
|
||||
t.deepEqual(result, {
|
||||
...configFile,
|
||||
"default-setup": { org: { "model-packs": [123] } },
|
||||
});
|
||||
t.deepEqual(result, {
|
||||
...configFile,
|
||||
"default-setup": { org: { "model-packs": [123] } },
|
||||
});
|
||||
|
||||
const expectedInvalidKeys = [".default-setup.org.model-packs[0]"].join(", ");
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Invalid keys in Default Setup configuration: ${expectedInvalidKeys}`,
|
||||
]);
|
||||
});
|
||||
const expectedInvalidKeys = [".default-setup.org.model-packs[0]"].join(
|
||||
", ",
|
||||
);
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Invalid keys in Default Setup configuration: ${expectedInvalidKeys}`,
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/** Parses `contents` as a configuration without validating it. */
|
||||
function parseUnvalidatedConfig(contents: string): dbConfig.UserConfig {
|
||||
return dbConfig.parseUserConfig(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
"test",
|
||||
contents,
|
||||
false,
|
||||
|
||||
165
src/diagnostics.test.ts
Normal file
165
src/diagnostics.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import test, { type ExecutionContext } from "ava";
|
||||
|
||||
import * as diagnostics from "./diagnostics";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
createTestConfig,
|
||||
initAllState,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import { withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
const id = "codeql-action/test-diagnostic";
|
||||
const name = "Test title";
|
||||
|
||||
test("makeDiagnostic - adds expected properties", (t) => {
|
||||
const diagnostic = diagnostics.makeDiagnostic(id, name, {});
|
||||
|
||||
t.truthy(diagnostic.timestamp);
|
||||
t.is(diagnostic.source.id, id);
|
||||
t.is(diagnostic.source.name, name);
|
||||
});
|
||||
|
||||
test("getLanguageDiagnosticsPath gets language-specific path", async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
for (const language of Object.values(BuiltInLanguage)) {
|
||||
t.is(
|
||||
diagnostics.getLanguageDiagnosticsPath(tmpDir, language),
|
||||
path.resolve(
|
||||
tmpDir,
|
||||
language,
|
||||
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
|
||||
),
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
test("getTempLanguageDiagnosticsPath returns path without language", async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
t.is(
|
||||
diagnostics.getTempLanguageDiagnosticsPath(tmpDir),
|
||||
path.resolve(
|
||||
tmpDir,
|
||||
diagnostics.TEMP_DIAGNOSTIC_DIR_NAME,
|
||||
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
test("getTempLanguageDiagnosticsPath returns language-specific path if a language is provided", async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
t.is(
|
||||
diagnostics.getTempLanguageDiagnosticsPath(tmpDir, BuiltInLanguage.swift),
|
||||
path.resolve(
|
||||
tmpDir,
|
||||
diagnostics.TEMP_DIAGNOSTIC_DIR_NAME,
|
||||
BuiltInLanguage.swift,
|
||||
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
test("getDatabaseDiagnosticsPath returns language-specific path", async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
t.is(
|
||||
diagnostics.getDatabaseDiagnosticsPath(
|
||||
createTestConfig({ dbLocation: tmpDir }),
|
||||
BuiltInLanguage.swift,
|
||||
),
|
||||
path.resolve(
|
||||
tmpDir,
|
||||
BuiltInLanguage.swift,
|
||||
...diagnostics.CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
/**
|
||||
* Diagnostic files that are written to disk don't have deterministic names.
|
||||
* This function recursively finds all `.json` files at {@link rootPath},
|
||||
* asserts that there is exactly one, and that it matches {@link expected}.
|
||||
*
|
||||
* @param rootPath The path to start searching from.
|
||||
*/
|
||||
function assertSingleDiagnosticIn(
|
||||
t: ExecutionContext<unknown>,
|
||||
rootPath: string,
|
||||
expected: diagnostics.DiagnosticMessage,
|
||||
) {
|
||||
// Find all `.json` files in `rootPath` and check that there is exactly one.
|
||||
const filePaths = fs
|
||||
.readdirSync(rootPath, {
|
||||
recursive: true,
|
||||
encoding: "utf-8",
|
||||
})
|
||||
.filter((p) => p.endsWith(".json"));
|
||||
|
||||
t.is(filePaths.length, 1);
|
||||
|
||||
// Check that the written diagnostic matches the one we added.
|
||||
const contents = fs.readFileSync(
|
||||
path.resolve(rootPath, filePaths[0]),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const readDiagnostic = JSON.parse(contents) as diagnostics.DiagnosticMessage;
|
||||
t.deepEqual(readDiagnostic, expected);
|
||||
}
|
||||
|
||||
test("addDiagnostic writes temporary diagnostics and flushDiagnostics moves them to the database", async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const tempDiagDir = path.resolve(tmpDir, "temp");
|
||||
const testDbDir = path.resolve(tmpDir, "test", "database");
|
||||
const diagnostic = diagnostics.makeDiagnostic(id, name, {});
|
||||
|
||||
// Write the diagnostic. The path provided for `dbLocation` doesn't exist,
|
||||
// so the diagnostic will be written to a temporary location.
|
||||
t.notThrows(() => {
|
||||
diagnostics.addDiagnostic(
|
||||
createTestConfig({ tempDir: tempDiagDir, dbLocation: testDbDir }),
|
||||
BuiltInLanguage.actions,
|
||||
diagnostic,
|
||||
logger,
|
||||
);
|
||||
});
|
||||
|
||||
// Check that `tempDiagDir` was created.
|
||||
t.true(fs.existsSync(tempDiagDir));
|
||||
|
||||
// Check that the diagnostic exists under `tempDiagDir`.
|
||||
assertSingleDiagnosticIn(t, tempDiagDir, diagnostic);
|
||||
|
||||
// Flush the diagnostics. This will cause the diagnostic we wrote previously to
|
||||
// be moved to a location under `dbLocation`.
|
||||
t.notThrows(() => {
|
||||
diagnostics.flushDiagnostics(
|
||||
initAllState({ logger }),
|
||||
createTestConfig({
|
||||
tempDir: tempDiagDir,
|
||||
dbLocation: testDbDir,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Check that `testDbDir` was created.
|
||||
t.true(fs.existsSync(testDbDir));
|
||||
|
||||
// Check that the diagnostic exists under `testDbDir`.
|
||||
assertSingleDiagnosticIn(t, testDbDir, diagnostic);
|
||||
|
||||
// Check that we logged that the database directory doesn't exist yet
|
||||
// and that we moved one diagnostic.
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`but the database at ${path.join(testDbDir, "actions")} does not exist yet`,
|
||||
"Moving 1 diagnostic(s) to their respective databases.",
|
||||
]);
|
||||
}));
|
||||
@@ -1,10 +1,24 @@
|
||||
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
import { getTemporaryDirectory } from "./actions-util";
|
||||
import type { Config } from "./config-utils";
|
||||
import { Language } from "./languages";
|
||||
import { getActionsLogger } from "./logging";
|
||||
import { getCodeQLDatabasePath } from "./util";
|
||||
import { getActionsLogger, type Logger } from "./logging";
|
||||
import {
|
||||
getCodeQLDatabasePath,
|
||||
getErrorMessage,
|
||||
isErrnoException,
|
||||
} from "./util";
|
||||
|
||||
/** The name of the directory we use to temporarily store diagnostics. */
|
||||
export const TEMP_DIAGNOSTIC_DIR_NAME = "codeql-action-temp-diagnostics";
|
||||
|
||||
export const CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS = [
|
||||
"diagnostic",
|
||||
"codeql-action",
|
||||
];
|
||||
|
||||
/**
|
||||
* Known tags for diagnostics. There is currently only "internal-error",
|
||||
@@ -84,22 +98,16 @@ export type DiagnosticMessage = DiagnosticMessageOptions & {
|
||||
source: DiagnosticSource;
|
||||
};
|
||||
|
||||
/** Represents a diagnostic message that has not yet been written to the database. */
|
||||
interface UnwrittenDiagnostic {
|
||||
/** The diagnostic message that has not yet been written. */
|
||||
diagnostic: DiagnosticMessage;
|
||||
/** The language the diagnostic is for. */
|
||||
language: Language;
|
||||
/** Represents a diagnostic message that has not yet been saved to the database. */
|
||||
interface TemporaryDiagnostic {
|
||||
/** The path to which the diagnostic has temporarily been written to. */
|
||||
path: string;
|
||||
/** The language the diagnostic is for, if any. */
|
||||
language?: Language;
|
||||
}
|
||||
|
||||
/** A list of diagnostics which have not yet been written to disk. */
|
||||
let unwrittenDiagnostics: UnwrittenDiagnostic[] = [];
|
||||
|
||||
/**
|
||||
* A list of diagnostics which have not yet been written to disk,
|
||||
* and where the language does not matter.
|
||||
*/
|
||||
let unwrittenDefaultLanguageDiagnostics: DiagnosticMessage[] = [];
|
||||
/** A list of diagnostics which have not yet been saved to the database. */
|
||||
let temporaryDiagnostics: TemporaryDiagnostic[] = [];
|
||||
|
||||
/**
|
||||
* Counter used to generate a unique suffix for each diagnostic filename, so that
|
||||
@@ -128,6 +136,51 @@ export function makeDiagnostic(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a path relative to {@link basePath} where diagnostics for {@link language} should be stored at.
|
||||
*/
|
||||
export function getLanguageDiagnosticsPath(
|
||||
basePath: string,
|
||||
language: Language,
|
||||
) {
|
||||
return path.resolve(
|
||||
basePath,
|
||||
language,
|
||||
...CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a path relative to {@link tmpDir} where diagnostics should temporarily be stored
|
||||
* until they can be moved to the database.
|
||||
*/
|
||||
export function getTempDiagnosticsRoot(tmpDir: string): string {
|
||||
return path.resolve(tmpDir, TEMP_DIAGNOSTIC_DIR_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a path relative to {@link tmpDir} where diagnostics for {@link language}
|
||||
* can temporarily be stored at before the database is initialised. If {@link language}
|
||||
* is `undefined`, then a non-language-specific path is returned.
|
||||
*/
|
||||
export function getTempLanguageDiagnosticsPath(
|
||||
tmpDir: string,
|
||||
language?: Language,
|
||||
) {
|
||||
const root = getTempDiagnosticsRoot(tmpDir);
|
||||
|
||||
if (language !== undefined) {
|
||||
return getLanguageDiagnosticsPath(root, language);
|
||||
}
|
||||
|
||||
return path.resolve(root, ...CODEQL_ACTION_DIAGNOSTIC_SUBDIR_PARTS);
|
||||
}
|
||||
|
||||
/** Gets the path where diagnostics for {@link language} should be stored in the database. */
|
||||
export function getDatabaseDiagnosticsPath(config: Config, language: Language) {
|
||||
return getLanguageDiagnosticsPath(config.dbLocation, language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given diagnostic to the database. If the database does not yet exist,
|
||||
* the diagnostic will be written to it once it has been created.
|
||||
@@ -140,22 +193,23 @@ export function addDiagnostic(
|
||||
config: Config,
|
||||
language: Language,
|
||||
diagnostic: DiagnosticMessage,
|
||||
logger: Logger = getActionsLogger(),
|
||||
) {
|
||||
const logger = getActionsLogger();
|
||||
const databasePath = language
|
||||
? getCodeQLDatabasePath(config, language)
|
||||
: config.dbLocation;
|
||||
|
||||
// Check that the database exists before writing to it. If the database does not yet exist,
|
||||
// store the diagnostic in memory and write it later.
|
||||
if (existsSync(databasePath)) {
|
||||
writeDiagnostic(config, language, diagnostic);
|
||||
// store the diagnostic in a temporary location and move it to the database later.
|
||||
if (fs.existsSync(databasePath)) {
|
||||
writeDiagnostic({ logger }, config, language, diagnostic);
|
||||
} else {
|
||||
logger.debug(
|
||||
`Writing a diagnostic for ${language}, but the database at ${databasePath} does not exist yet.`,
|
||||
);
|
||||
|
||||
unwrittenDiagnostics.push({ diagnostic, language });
|
||||
// Write the diagnostic to a temporary location.
|
||||
writeTemporaryDiagnosticFile(logger, config.tempDir, language, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +217,7 @@ export function addDiagnostic(
|
||||
export function addNoLanguageDiagnostic(
|
||||
config: Config | undefined,
|
||||
diagnostic: DiagnosticMessage,
|
||||
logger: Logger = getActionsLogger(),
|
||||
) {
|
||||
if (config !== undefined) {
|
||||
addDiagnostic(
|
||||
@@ -173,10 +228,74 @@ export function addNoLanguageDiagnostic(
|
||||
diagnostic,
|
||||
);
|
||||
} else {
|
||||
unwrittenDefaultLanguageDiagnostics.push(diagnostic);
|
||||
logger.debug(
|
||||
`Writing a diagnostic not associated with any language, but we have no configuration to tell us where the database is yet.`,
|
||||
);
|
||||
|
||||
// Write the diagnostic to a temporary location.
|
||||
writeTemporaryDiagnosticFile(
|
||||
logger,
|
||||
getTemporaryDirectory(),
|
||||
undefined,
|
||||
diagnostic,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@link diagnostic} for an optional {@link language} to a subdirectory of {@link tempDir}
|
||||
* and tracks it as a diagnostic that was written to a temporary location.
|
||||
*/
|
||||
function writeTemporaryDiagnosticFile(
|
||||
logger: Logger,
|
||||
tempDir: string,
|
||||
language: Language | undefined,
|
||||
diagnostic: DiagnosticMessage,
|
||||
) {
|
||||
const tempDiagnosticsPath = getTempLanguageDiagnosticsPath(tempDir, language);
|
||||
|
||||
try {
|
||||
// Write the diagnostic to a temporary location.
|
||||
const diagnosticPath = writeDiagnosticFile(tempDiagnosticsPath, diagnostic);
|
||||
|
||||
// Track the temporary file.
|
||||
temporaryDiagnostics.push({ path: diagnosticPath, language });
|
||||
} catch (err) {
|
||||
logger.warning(
|
||||
`Unable to write temporary diagnostic to '${tempDiagnosticsPath}': ${getErrorMessage(err)}\n\n${JSON.stringify(diagnostic)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@link diagnostic} to a file in {@link diagnosticsPath}.
|
||||
*/
|
||||
function writeDiagnosticFile(
|
||||
diagnosticsPath: string,
|
||||
diagnostic: DiagnosticMessage,
|
||||
): string {
|
||||
// Create the directory if it doesn't exist yet.
|
||||
fs.mkdirSync(diagnosticsPath, { recursive: true });
|
||||
|
||||
// Include a monotonically increasing suffix to avoid filename collisions
|
||||
// between diagnostics produced within the same millisecond.
|
||||
const uniqueSuffix = (diagnosticCounter++).toString();
|
||||
// We should only need to remove colons, but to be defensive, only allow a restricted set of
|
||||
// characters.
|
||||
const sanitizedTimestamp = diagnostic.timestamp.replace(
|
||||
/[^a-zA-Z0-9.-]/g,
|
||||
"",
|
||||
);
|
||||
const jsonPath = path.resolve(
|
||||
diagnosticsPath,
|
||||
`codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json`,
|
||||
);
|
||||
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(diagnostic));
|
||||
|
||||
return jsonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given diagnostic to the database.
|
||||
*
|
||||
@@ -185,78 +304,94 @@ export function addNoLanguageDiagnostic(
|
||||
* @param diagnostic The diagnostic message to add to the database.
|
||||
*/
|
||||
function writeDiagnostic(
|
||||
action: ActionState<["Logger"]>,
|
||||
config: Config,
|
||||
language: Language | undefined,
|
||||
language: Language,
|
||||
diagnostic: DiagnosticMessage,
|
||||
) {
|
||||
const logger = getActionsLogger();
|
||||
const databasePath = language
|
||||
? getCodeQLDatabasePath(config, language)
|
||||
: config.dbLocation;
|
||||
const diagnosticsPath = path.resolve(
|
||||
databasePath,
|
||||
"diagnostic",
|
||||
"codeql-action",
|
||||
);
|
||||
const diagnosticsPath = getDatabaseDiagnosticsPath(config, language);
|
||||
|
||||
try {
|
||||
// Create the directory if it doesn't exist yet.
|
||||
mkdirSync(diagnosticsPath, { recursive: true });
|
||||
|
||||
// Include a monotonically increasing suffix to avoid filename collisions
|
||||
// between diagnostics produced within the same millisecond.
|
||||
const uniqueSuffix = (diagnosticCounter++).toString();
|
||||
// We should only need to remove colons, but to be defensive, only allow a restricted set of
|
||||
// characters.
|
||||
const sanitizedTimestamp = diagnostic.timestamp.replace(
|
||||
/[^a-zA-Z0-9.-]/g,
|
||||
"",
|
||||
);
|
||||
const jsonPath = path.resolve(
|
||||
diagnosticsPath,
|
||||
`codeql-action-${sanitizedTimestamp}-${uniqueSuffix}.json`,
|
||||
);
|
||||
|
||||
writeFileSync(jsonPath, JSON.stringify(diagnostic));
|
||||
writeDiagnosticFile(diagnosticsPath, diagnostic);
|
||||
} catch (err) {
|
||||
logger.warning(`Unable to write diagnostic message to database: ${err}`);
|
||||
logger.debug(JSON.stringify(diagnostic));
|
||||
action.logger.warning(
|
||||
`Unable to write diagnostic message to database: ${err}`,
|
||||
);
|
||||
action.logger.debug(JSON.stringify(diagnostic));
|
||||
}
|
||||
}
|
||||
|
||||
/** Report if there are unwritten diagnostics and write them to the log. */
|
||||
export function logUnwrittenDiagnostics() {
|
||||
const logger = getActionsLogger();
|
||||
const num = unwrittenDiagnostics.length;
|
||||
if (num > 0) {
|
||||
logger.warning(
|
||||
`${num} diagnostic(s) could not be written to the database and will not appear on the Tool Status Page.`,
|
||||
/** Report if there are temporary diagnostics and write them to the log. */
|
||||
export function logTemporaryDiagnostics(action: ActionState<["Logger"]>) {
|
||||
const num = temporaryDiagnostics.length;
|
||||
|
||||
if (num > 0 && action.logger.isDebug()) {
|
||||
action.logger.startGroup("Diagnostics in temporary location");
|
||||
action.logger.debug(
|
||||
`${num} diagnostic(s) were not moved to the database and remain in the temporary directory.`,
|
||||
);
|
||||
|
||||
for (const unwritten of unwrittenDiagnostics) {
|
||||
logger.debug(JSON.stringify(unwritten.diagnostic));
|
||||
for (const temporary of temporaryDiagnostics) {
|
||||
try {
|
||||
action.logger.debug(fs.readFileSync(temporary.path, "utf-8"));
|
||||
} catch (err) {
|
||||
action.logger.debug(
|
||||
`Failed to read diagnostics from temporary location at '${temporary.path}': ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
action.logger.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
/** Relocates all temporary diagnostics to the respective databases. */
|
||||
export function flushDiagnostics(
|
||||
action: ActionState<["Logger"]>,
|
||||
config: Config,
|
||||
) {
|
||||
const diagnosticsCount = temporaryDiagnostics.length;
|
||||
action.logger.debug(
|
||||
`Moving ${diagnosticsCount} diagnostic(s) to their respective databases.`,
|
||||
);
|
||||
|
||||
const failedFlushDiagnostics: TemporaryDiagnostic[] = [];
|
||||
for (const temporary of temporaryDiagnostics) {
|
||||
// If `temporary.language` is `undefined`, then we didn't have a `config` at the time that
|
||||
// `addNoLanguageDiagnostic` was called. In that case, we arbitrarily choose the first
|
||||
// configured language here like we would have done in `addNoLanguageDiagnostic`.
|
||||
const directory = getDatabaseDiagnosticsPath(
|
||||
config,
|
||||
temporary.language ?? config.languages[0],
|
||||
);
|
||||
const filename = path.basename(temporary.path);
|
||||
const destination = path.join(directory, filename);
|
||||
|
||||
try {
|
||||
fs.mkdirSync(directory, { recursive: true });
|
||||
try {
|
||||
fs.renameSync(temporary.path, destination);
|
||||
} catch (renameErr: unknown) {
|
||||
// Rethrow the error if it isn't an EXDEV one.
|
||||
if (!isErrnoException(renameErr) || renameErr.code !== "EXDEV") {
|
||||
throw renameErr;
|
||||
}
|
||||
|
||||
// The error was an EXDEV one: try to copy the file and delete the temporary one.
|
||||
fs.copyFileSync(temporary.path, destination);
|
||||
fs.unlinkSync(temporary.path);
|
||||
}
|
||||
} catch (err) {
|
||||
action.logger.warning(
|
||||
`Failed to relocate '${temporary.path}' to '${destination}': ${getErrorMessage(err)}`,
|
||||
);
|
||||
|
||||
failedFlushDiagnostics.push(temporary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes all unwritten diagnostics to disk. */
|
||||
export function flushDiagnostics(config: Config) {
|
||||
const logger = getActionsLogger();
|
||||
|
||||
const diagnosticsCount =
|
||||
unwrittenDiagnostics.length + unwrittenDefaultLanguageDiagnostics.length;
|
||||
logger.debug(`Writing ${diagnosticsCount} diagnostic(s) to database.`);
|
||||
|
||||
for (const unwritten of unwrittenDiagnostics) {
|
||||
writeDiagnostic(config, unwritten.language, unwritten.diagnostic);
|
||||
}
|
||||
for (const unwritten of unwrittenDefaultLanguageDiagnostics) {
|
||||
addNoLanguageDiagnostic(config, unwritten);
|
||||
}
|
||||
|
||||
// Reset the unwritten diagnostics arrays.
|
||||
unwrittenDiagnostics = [];
|
||||
unwrittenDefaultLanguageDiagnostics = [];
|
||||
// Reset the temporary diagnostics arrays.
|
||||
temporaryDiagnostics = failedFlushDiagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
exportedForTesting,
|
||||
} from "./diff-informed-analysis-utils";
|
||||
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
import {
|
||||
setupTests,
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
mockFeatureFlagApiEndpoint,
|
||||
setupActionsVars,
|
||||
makeMacro,
|
||||
RecordingLogger,
|
||||
} from "./testing-utils";
|
||||
import { GitHubVariant, withTmpDir } from "./util";
|
||||
import type { GitHubVersion } from "./util";
|
||||
@@ -55,7 +55,7 @@ const testShouldPerformDiffInformedAnalysis = makeMacro({
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
const testCase = { ...defaultTestCase, ...partialTestCase };
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const codeql = mockCodeQLVersion(testCase.codeQLVersion);
|
||||
|
||||
if (testCase.diffInformedQueriesEnvVar !== undefined) {
|
||||
@@ -182,7 +182,7 @@ test.serial(
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const codeql = mockCodeQLVersion("2.21.0");
|
||||
const features = createFeatures([Feature.DiffInformedQueries]);
|
||||
|
||||
@@ -207,7 +207,7 @@ test.serial(
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const codeql = mockCodeQLVersion("2.21.0");
|
||||
// A features implementation whose getValue rejects, simulating an
|
||||
// unexpected failure when determining whether diff-informed analysis
|
||||
@@ -237,7 +237,7 @@ test.serial(
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const codeql = mockCodeQLVersion("2.21.0");
|
||||
const features = createFeatures([Feature.DiffInformedQueries]);
|
||||
|
||||
@@ -274,7 +274,7 @@ test.serial(
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const codeql = mockCodeQLVersion("2.21.0");
|
||||
const features = createFeatures([Feature.DiffInformedQueries]);
|
||||
|
||||
@@ -313,7 +313,7 @@ function runGetDiffRanges(changes: number, patch: string[] | undefined): any {
|
||||
changes,
|
||||
patch: patch?.join("\n"),
|
||||
},
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
export enum DocUrl {
|
||||
ASSIGNING_PERMISSIONS_TO_JOBS = "https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs",
|
||||
AUTOMATIC_BUILD_FAILED = "https://docs.github.com/en/code-security/code-scanning/troubleshooting-code-scanning/automatic-build-failed",
|
||||
CODEQL_BUILD_MODES = "https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#codeql-build-modes",
|
||||
DEFINE_ENV_VARIABLES = "https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow",
|
||||
|
||||
@@ -74,7 +74,6 @@ export enum Feature {
|
||||
AllowMergeConfigFiles = "allow_merge_config_files",
|
||||
/** Controls whether we allow multiple values for the `analysis-kinds` input. */
|
||||
AllowMultipleAnalysisKinds = "allow_multiple_analysis_kinds",
|
||||
CleanupTrapCaches = "cleanup_trap_caches",
|
||||
/** Whether to allow the `config-file` input to be specified via a repository property. */
|
||||
ConfigFileRepositoryProperty = "config_file_repository_property",
|
||||
CppDependencyInstallation = "cpp_dependency_installation_enabled",
|
||||
@@ -106,19 +105,6 @@ export enum Feature {
|
||||
OverlayAnalysisGo = "overlay_analysis_go",
|
||||
OverlayAnalysisJava = "overlay_analysis_java",
|
||||
OverlayAnalysisJavascript = "overlay_analysis_javascript",
|
||||
/**
|
||||
* When set, chooses the default CodeQL CLI version as the highest version that is both enabled by
|
||||
* feature flags and present as an overlay-base database in the Actions cache for the configured
|
||||
* languages. Falls back to the highest feature flagged version if no intersecting overlay-base
|
||||
* database exists in the cache.
|
||||
*/
|
||||
OverlayAnalysisMatchCodeqlVersion = "overlay_analysis_match_codeql_version",
|
||||
/**
|
||||
* Like `OverlayAnalysisMatchCodeqlVersion`, but only logs a diagnostic with the version that
|
||||
* would have been chosen instead of actually changing the default CodeQL CLI version.
|
||||
* `OverlayAnalysisMatchCodeqlVersion` overrides this flag.
|
||||
*/
|
||||
OverlayAnalysisMatchCodeqlVersionDryRun = "overlay_analysis_match_codeql_version_dry_run",
|
||||
/**
|
||||
* Lowers the overlay minimum available disk space to 8 GB. The lowest enabled limit wins; if
|
||||
* none are enabled, the default applies.
|
||||
@@ -214,11 +200,6 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CleanupTrapCaches]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ConfigFileRepositoryProperty]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CONFIG_FILE_REPOSITORY_PROPERTY",
|
||||
@@ -367,16 +348,6 @@ export const featureConfig = {
|
||||
minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY,
|
||||
},
|
||||
// Other overlay-related feature flags
|
||||
[Feature.OverlayAnalysisMatchCodeqlVersion]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisMatchCodeqlVersionDryRun]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION_DRY_RUN",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisMinDisk8Gb]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MIN_DISK_8_GB",
|
||||
|
||||
@@ -2,7 +2,6 @@ import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as api from "../api-client";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import { parseRepositoryNwo } from "../repository";
|
||||
import { RecordingLogger, setupTests } from "../testing-utils";
|
||||
|
||||
@@ -19,7 +18,7 @@ test.serial(
|
||||
url: "",
|
||||
data: {},
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
|
||||
@@ -39,7 +38,7 @@ test.serial(
|
||||
url: "",
|
||||
data: [{}],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
|
||||
@@ -63,7 +62,7 @@ test.serial(
|
||||
{ property_name: "also-not-used-by-us", value: ["A", "B", "C"] },
|
||||
],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.notThrowsAsync(
|
||||
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
|
||||
@@ -86,7 +85,7 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
{ property_name: "unknown-property", value: "something" },
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
logger,
|
||||
@@ -113,7 +112,7 @@ test.serial("loadPropertiesFromApi parses true boolean property", async (t) => {
|
||||
{ property_name: "github-codeql-extra-queries", value: "+queries" },
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
@@ -141,7 +140,7 @@ test.serial(
|
||||
},
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
@@ -164,7 +163,7 @@ test.serial(
|
||||
url: "",
|
||||
data: [{ property_name: "github-codeql-extra-queries", value: 123 }],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(logger, mockRepositoryNwo),
|
||||
@@ -190,7 +189,7 @@ test.serial(
|
||||
},
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
|
||||
@@ -8,11 +8,12 @@ import {
|
||||
FeatureWithoutCLI,
|
||||
initFeatures,
|
||||
} from "../feature-flags";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import type { Logger } from "../logging";
|
||||
import { parseRepositoryNwo } from "../repository";
|
||||
import {
|
||||
LoggedMessage,
|
||||
mockCodeQLVersion,
|
||||
RecordingLogger,
|
||||
setupActionsVars,
|
||||
} from "../testing-utils";
|
||||
import { ToolsFeature } from "../tools-features";
|
||||
@@ -51,7 +52,7 @@ export function assertAllFeaturesUndefinedInApi(
|
||||
|
||||
export function setUpFeatureFlagTests(
|
||||
tmpDir: string,
|
||||
logger = getRunnerLogger(true),
|
||||
logger: Logger = new RecordingLogger(),
|
||||
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion,
|
||||
): FeatureEnablement {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
@@ -5,9 +5,8 @@ import * as ava from "ava";
|
||||
import test from "ava";
|
||||
|
||||
import * as fingerprints from "./fingerprints";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import * as sarif from "./sarif";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { RecordingLogger, setupTests } from "./testing-utils";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -131,7 +130,7 @@ function testResolveUriToFile(uri: any, index: any, artifactsURIs: any[]) {
|
||||
location,
|
||||
artifacts,
|
||||
process.cwd(),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -218,7 +217,7 @@ test("addFingerprints", async (t) => {
|
||||
await fingerprints.addFingerprints(
|
||||
input,
|
||||
sourceRoot,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
expected,
|
||||
);
|
||||
@@ -246,7 +245,7 @@ test("missingRegions", async (t) => {
|
||||
await fingerprints.addFingerprints(
|
||||
input,
|
||||
sourceRoot,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
expected,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import * as sinon from "sinon";
|
||||
@@ -8,10 +11,10 @@ import * as apiClient from "./api-client";
|
||||
import * as codeql from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import * as debugArtifacts from "./debug-artifacts";
|
||||
import { getTempDiagnosticsRoot } from "./diagnostics";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as initActionPostHelper from "./init-action-post-helper";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import * as overlayStatus from "./overlay/status";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
@@ -20,6 +23,7 @@ import {
|
||||
createFeatures,
|
||||
createTestConfig,
|
||||
DEFAULT_ACTIONS_VARS,
|
||||
getCodeQLForTesting,
|
||||
getTestEnv,
|
||||
makeMacro,
|
||||
makeVersionInfo,
|
||||
@@ -35,6 +39,47 @@ const NUM_BYTES_PER_GIB = 1024 * 1024 * 1024;
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial("generateFailedSarif picks up unwritten diagnostics", async (t) =>
|
||||
util.withTmpDir(async (tmpDir) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const tempDiagDir = getTempDiagnosticsRoot(tmpDir);
|
||||
fs.mkdirSync(tempDiagDir, { recursive: true });
|
||||
|
||||
const codeqlObject = await getCodeQLForTesting();
|
||||
const diagnosticsExport = sinon
|
||||
.stub(codeqlObject, "diagnosticsExport")
|
||||
.resolves();
|
||||
|
||||
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
|
||||
|
||||
const sarifFile = path.join(tmpDir, "failed.sarif");
|
||||
const result = await initActionPostHelper.generateFailedSarif(
|
||||
logger,
|
||||
createFeatures([Feature.ExportDiagnosticsEnabled]),
|
||||
createTestConfig({
|
||||
tempDir: tmpDir,
|
||||
// Explicitly undefined to ensure we get the "no database" path.
|
||||
dbLocation: undefined,
|
||||
}),
|
||||
"category",
|
||||
"",
|
||||
sarifFile,
|
||||
);
|
||||
|
||||
t.truthy(result);
|
||||
t.is(diagnosticsExport.callCount, 1);
|
||||
t.true(
|
||||
diagnosticsExport.calledOnceWith(
|
||||
sarifFile,
|
||||
"category",
|
||||
sinon.match.any,
|
||||
tempDiagDir,
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
test.serial("init-post action with debug mode off", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -62,7 +107,7 @@ test.serial("init-post action with debug mode off", async (t) => {
|
||||
createFeatures([]),
|
||||
"success",
|
||||
getTestEnv(),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(uploadAllAvailableDebugArtifactsSpy.notCalled);
|
||||
@@ -86,7 +131,7 @@ test.serial("init-post action with debug mode on", async (t) => {
|
||||
createFeatures([]),
|
||||
"success",
|
||||
getTestEnv(),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(uploadAllAvailableDebugArtifactsSpy.called);
|
||||
@@ -383,7 +428,7 @@ test.serial(
|
||||
createFeatures([Feature.OverlayAnalysisStatusSave]),
|
||||
"success",
|
||||
getTestEnv(),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.true(
|
||||
@@ -453,7 +498,7 @@ test.serial(
|
||||
createFeatures([]),
|
||||
"success",
|
||||
getTestEnv(),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.true(
|
||||
@@ -497,7 +542,7 @@ test.serial("does not save overlay status when build successful", async (t) => {
|
||||
createFeatures([Feature.OverlayAnalysisStatusSave]),
|
||||
"success",
|
||||
env,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.true(
|
||||
@@ -536,7 +581,7 @@ test.serial(
|
||||
createFeatures([]),
|
||||
"success",
|
||||
getTestEnv(),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.true(
|
||||
@@ -589,7 +634,7 @@ async function runOverlayPostStep({
|
||||
createFeatures([Feature.OverlayAnalysisStatusSave]),
|
||||
jobStatus,
|
||||
env,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
return { saveOverlayStatusStub };
|
||||
@@ -737,7 +782,7 @@ async function testFailedSarifUpload(
|
||||
.withArgs("matrix")
|
||||
.returns(JSON.stringify(matrix));
|
||||
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
const codeqlObject = await getCodeQLForTesting();
|
||||
sinon.stub(codeql, "getCodeQL").resolves(codeqlObject);
|
||||
sinon.stub(codeqlObject, "getVersion").resolves(makeVersionInfo("2.17.6"));
|
||||
const databaseExportDiagnosticsStub = sinon.stub(
|
||||
@@ -764,7 +809,7 @@ async function testFailedSarifUpload(
|
||||
config,
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures(features),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
if (expectUpload) {
|
||||
t.deepEqual(result, {
|
||||
@@ -833,7 +878,7 @@ async function mockRiskAssessmentEnv(matrix: string) {
|
||||
.stub(apiClient, "getGitHubVersion")
|
||||
.resolves({ type: util.GitHubVariant.GHES, version: "3.0.0" });
|
||||
|
||||
const codeqlObject = await codeql.getCodeQLForTesting();
|
||||
const codeqlObject = await getCodeQLForTesting();
|
||||
const databaseExportDiagnostics = sinon
|
||||
.stub(codeqlObject, "databaseExportDiagnostics")
|
||||
.resolves();
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
type UploadDebugArtifactsConfig,
|
||||
} from "./debug-artifacts";
|
||||
import * as dependencyCaching from "./dependency-caching";
|
||||
import { getTempDiagnosticsRoot } from "./diagnostics";
|
||||
import { EnvVar, ReadOnlyEnv } from "./environment";
|
||||
import { Feature, FeatureEnablement } from "./feature-flags";
|
||||
import { Logger } from "./logging";
|
||||
@@ -177,7 +178,16 @@ async function prepareFailedSarif(
|
||||
}
|
||||
}
|
||||
|
||||
async function generateFailedSarif(
|
||||
/**
|
||||
* Generates a SARIF file for a failed run, i.e. an analysis where no SARIF was produced as a result of the analysis.
|
||||
*
|
||||
* Not intended to be called directly, other than by `prepareFailedSarif`.
|
||||
*
|
||||
* @param category The analysis category to include in the SARIF.
|
||||
* @param checkoutPath The path where the repository was checked out at.
|
||||
* @param sarifFile The SARIF file to produce.
|
||||
*/
|
||||
export async function generateFailedSarif(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir">,
|
||||
@@ -194,11 +204,26 @@ async function generateFailedSarif(
|
||||
}
|
||||
|
||||
// If there is no database or the feature flag is off, we run 'export diagnostics'
|
||||
if (
|
||||
databasePath === undefined ||
|
||||
!(await features.getValue(Feature.ExportDiagnosticsEnabled, codeql))
|
||||
) {
|
||||
await codeql.diagnosticsExport(sarifFile, category, config);
|
||||
const exportDiagnosticsEnabled = await features.getValue(
|
||||
Feature.ExportDiagnosticsEnabled,
|
||||
codeql,
|
||||
);
|
||||
if (databasePath === undefined || !exportDiagnosticsEnabled) {
|
||||
// If we don't have a database path, then we may have diagnostics that were written to a
|
||||
// temporary location. Get the path that they would have been written to and check whether
|
||||
// it exists. If so, pass it on to the CLI. The CLI will not complain if we give it a
|
||||
// path that doesn't exist, so we are just being extra defensive here.
|
||||
// We also don't pass the path to the CLI if `exportDiagnosticsEnabled` is not enabled, to preserve
|
||||
// its intended behaviour of controlling whether diagnostics are included in the SARIF or not.
|
||||
let diagnosticDir: string | undefined = getTempDiagnosticsRoot(
|
||||
config.tempDir,
|
||||
);
|
||||
|
||||
if (!exportDiagnosticsEnabled || !fs.existsSync(diagnosticDir)) {
|
||||
diagnosticDir = undefined;
|
||||
}
|
||||
|
||||
await codeql.diagnosticsExport(sarifFile, category, config, diagnosticDir);
|
||||
} else {
|
||||
// We call 'database export-diagnostics' to find any per-database diagnostics.
|
||||
await codeql.databaseExportDiagnostics(databasePath, sarifFile, category);
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
addDiagnostic,
|
||||
addNoLanguageDiagnostic,
|
||||
flushDiagnostics,
|
||||
logUnwrittenDiagnostics,
|
||||
logTemporaryDiagnostics,
|
||||
makeDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
@@ -484,6 +484,7 @@ async function run(
|
||||
if (statusReportBase !== undefined) {
|
||||
await sendStatusReport(statusReportBase);
|
||||
}
|
||||
logTemporaryDiagnostics(actionState);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -766,14 +767,15 @@ async function run(
|
||||
);
|
||||
}
|
||||
|
||||
// Write diagnostics to the database that we previously stored in memory because the database
|
||||
// did not exist until now.
|
||||
flushDiagnostics(config);
|
||||
// Move diagnostics to the database that we previously kept in a temporary location
|
||||
// because the database did not exist until now
|
||||
flushDiagnostics(actionState, config);
|
||||
|
||||
// We save the config here instead of at the end of `initConfig` because we
|
||||
// may have updated the config returned from `initConfig`, e.g. to revert to
|
||||
// `OverlayDatabaseMode.None` if we failed to download an overlay-base
|
||||
// database.
|
||||
config.successfullyInitialised = true;
|
||||
await configUtils.saveConfig(config, logger);
|
||||
|
||||
core.setOutput("codeql-path", config.codeQLCmd);
|
||||
@@ -797,7 +799,7 @@ async function run(
|
||||
);
|
||||
return;
|
||||
} finally {
|
||||
logUnwrittenDiagnostics();
|
||||
logTemporaryDiagnostics(actionState);
|
||||
}
|
||||
await sendCompletedStatusReport(
|
||||
startedAt,
|
||||
|
||||
17
src/logging.test.ts
Normal file
17
src/logging.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import test from "ava";
|
||||
|
||||
import { joinMessageStrings } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("joinMessageStrings", async (t) => {
|
||||
// For strings and errors, it is the identity function.
|
||||
t.deepEqual(joinMessageStrings("Hello"), "Hello");
|
||||
|
||||
const error = new Error("Some error");
|
||||
t.deepEqual(joinMessageStrings(error), error);
|
||||
|
||||
// For arrays of strings, we get a join-ed string.
|
||||
t.deepEqual(joinMessageStrings(["foo", "bar"]), "foo bar");
|
||||
});
|
||||
@@ -1,45 +1,92 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
export interface Logger {
|
||||
debug: (message: string) => void;
|
||||
info: (message: string) => void;
|
||||
warning: (message: string | Error) => void;
|
||||
error: (message: string | Error) => void;
|
||||
/** Types that all of our logging functions accept. */
|
||||
export type Loggable = string | string[];
|
||||
|
||||
/** Some logging functions also accept errors directly. */
|
||||
export type LoggableError = Loggable | Error;
|
||||
|
||||
export interface Logger {
|
||||
/**
|
||||
* Logs {@link message} at debug-level.
|
||||
* In Actions, this will only be output if workflow debugging is enabled.
|
||||
*
|
||||
* @param message
|
||||
* A string or an array of strings. If an array of strings is provided, they are joined using
|
||||
* space as the delimiter and printed as a single line.
|
||||
*/
|
||||
debug: (message: Loggable) => void;
|
||||
/**
|
||||
* Logs {@link message} at info-level.
|
||||
* In Actions, this will always be output.
|
||||
*
|
||||
* @param message
|
||||
* A string or an array of strings. If an array of strings is provided, they are joined using
|
||||
* space as the delimiter and printed as a single line.
|
||||
*/
|
||||
info: (message: Loggable) => void;
|
||||
/**
|
||||
* Logs {@link message} at warning-level.
|
||||
* In Actions, this will always be output and highlighted as a warning. It will additionally
|
||||
* result in a warning-level workflow annotation.
|
||||
*
|
||||
* @param message
|
||||
* A string or an array of strings. If an array of strings is provided, they are joined using
|
||||
* space as the delimiter and printed as a single line.
|
||||
*/
|
||||
warning: (message: LoggableError) => void;
|
||||
/**
|
||||
* Logs {@link message} at error-level.
|
||||
* In Actions, this will always be output and highlighted as an error. It will additionally
|
||||
* result in an error-level workflow annotation.
|
||||
*
|
||||
* @param message
|
||||
* A string or an array of strings. If an array of strings is provided, they are joined using
|
||||
* space as the delimiter and printed as a single line.
|
||||
*/
|
||||
error: (message: LoggableError) => void;
|
||||
/**
|
||||
* Gets a value indicating whether debugging is enabled.
|
||||
*/
|
||||
isDebug: () => boolean;
|
||||
|
||||
startGroup: (name: string) => void;
|
||||
endGroup: () => void;
|
||||
}
|
||||
|
||||
/** If `message` is an array of strings, the array is `join`-ed into one string separated by spaces. */
|
||||
export const joinMessageStrings = <T>(
|
||||
message: string[] | string | T,
|
||||
): string | T => {
|
||||
if (Array.isArray(message)) {
|
||||
return message.join(" ");
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a logging function so that `joinMessageStrings` is applied to the input before
|
||||
* calling the wrapped logging function.
|
||||
*/
|
||||
const autoJoinMessageStrings = <T>(fn: (msg: string | T) => void) => {
|
||||
return (message: string[] | string | T) => {
|
||||
return fn(joinMessageStrings(message));
|
||||
};
|
||||
};
|
||||
|
||||
export function getActionsLogger(): Logger {
|
||||
return {
|
||||
debug: core.debug,
|
||||
info: core.info,
|
||||
warning: core.warning,
|
||||
error: core.error,
|
||||
debug: autoJoinMessageStrings(core.debug),
|
||||
info: autoJoinMessageStrings(core.info),
|
||||
warning: autoJoinMessageStrings(core.warning),
|
||||
error: autoJoinMessageStrings(core.error),
|
||||
isDebug: core.isDebug,
|
||||
startGroup: core.startGroup,
|
||||
endGroup: core.endGroup,
|
||||
};
|
||||
}
|
||||
|
||||
export function getRunnerLogger(debugMode: boolean): Logger {
|
||||
return {
|
||||
// eslint-disable-next-line no-console
|
||||
debug: debugMode ? console.debug : () => undefined,
|
||||
// eslint-disable-next-line no-console
|
||||
info: console.info,
|
||||
// eslint-disable-next-line no-console
|
||||
warning: console.warn,
|
||||
// eslint-disable-next-line no-console
|
||||
error: console.error,
|
||||
isDebug: () => debugMode,
|
||||
startGroup: () => undefined,
|
||||
endGroup: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function withGroup<T>(groupName: string, f: () => T): T {
|
||||
core.startGroup(groupName);
|
||||
try {
|
||||
|
||||
@@ -11,11 +11,11 @@ import type { ResolveDatabaseOutput } from "../codeql";
|
||||
import * as environment from "../environment";
|
||||
import * as gitUtils from "../git-utils";
|
||||
import { BuiltInLanguage } from "../languages";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import {
|
||||
createTestConfig,
|
||||
makeMacro,
|
||||
mockCodeQLVersion,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
import * as utils from "../util";
|
||||
@@ -63,7 +63,7 @@ const testDownloadOverlayBaseDatabaseFromCache = makeMacro({
|
||||
const dbLocation = path.join(tmpDir, "db");
|
||||
await fs.promises.mkdir(dbLocation, { recursive: true });
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const testCase = { ...defaultDownloadTestCase, ...partialTestCase };
|
||||
const config = createTestConfig({
|
||||
dbLocation,
|
||||
@@ -214,7 +214,7 @@ testDownloadOverlayBaseDatabaseFromCache.serial(
|
||||
);
|
||||
|
||||
test.serial("overlay-base database cache keys remain stable", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const config = createTestConfig({ languages: ["python", "javascript"] });
|
||||
const codeQlVersion = "2.23.0";
|
||||
const commitOid = "abc123def456";
|
||||
@@ -261,7 +261,7 @@ test.serial("overlay-base database cache keys remain stable", async (t) => {
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases returns unique versions sorted latest first",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
sinon.stub(apiClient, "listActionsCaches").resolves([
|
||||
@@ -287,7 +287,7 @@ test.serial(
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases returns empty list when no caches exist",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
sinon.stub(apiClient, "listActionsCaches").resolves([]);
|
||||
@@ -303,7 +303,7 @@ test.serial(
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases returns empty list when cache keys are unparseable",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
sinon.stub(apiClient, "listActionsCaches").resolves([
|
||||
@@ -324,7 +324,7 @@ test.serial(
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases returns the single version when only one cache exists",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
sinon.stub(apiClient, "listActionsCaches").resolves([
|
||||
@@ -344,7 +344,7 @@ test.serial(
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases resolves language aliases",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
// The alias `c++` should be resolved to "cpp" and match cache entries keyed with "cpp"
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
@@ -365,7 +365,7 @@ test.serial(
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases de-duplicates resolved language aliases",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
const listActionsCachesStub = sinon
|
||||
@@ -391,7 +391,7 @@ test.serial(
|
||||
test.serial(
|
||||
"getCodeQlVersionsForOverlayBaseDatabases ignores nightly versions with build metadata",
|
||||
async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
sinon.stub(apiClient, "listActionsCaches").resolves([
|
||||
|
||||
@@ -6,8 +6,11 @@ import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "../actions-util";
|
||||
import * as gitUtils from "../git-utils";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import { createTestConfig, setupTests } from "../testing-utils";
|
||||
import {
|
||||
createTestConfig,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
import { withTmpDir } from "../util";
|
||||
|
||||
import { writeBaseDatabaseOidsFile, writeOverlayChangesFile } from ".";
|
||||
@@ -27,7 +30,7 @@ test.serial(
|
||||
),
|
||||
);
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const config = createTestConfig({ dbLocation });
|
||||
|
||||
// Mock the getFileOidsUnderPath function to return base OIDs
|
||||
@@ -91,7 +94,7 @@ test.serial(
|
||||
),
|
||||
);
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const config = createTestConfig({ dbLocation });
|
||||
|
||||
// Mock the getFileOidsUnderPath function to return base OIDs
|
||||
@@ -169,7 +172,7 @@ test.serial(
|
||||
),
|
||||
);
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const config = createTestConfig({ dbLocation });
|
||||
|
||||
// Mock the getFileOidsUnderPath function to return base OIDs
|
||||
@@ -232,7 +235,7 @@ test.serial(
|
||||
),
|
||||
);
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const config = createTestConfig({ dbLocation });
|
||||
|
||||
// Base OIDs (sourceRoot-relative paths)
|
||||
|
||||
@@ -14,7 +14,6 @@ import * as diagnostics from "./diagnostics";
|
||||
import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment";
|
||||
import { Feature } from "./feature-flags";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { getCacheRestoreKeyPrefix } from "./overlay/caching";
|
||||
import { MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION } from "./per-language-bundles";
|
||||
import * as setupCodeql from "./setup-codeql";
|
||||
@@ -22,6 +21,7 @@ import * as tar from "./tar";
|
||||
import {
|
||||
LINKED_CLI_VERSION,
|
||||
LoggedMessage,
|
||||
RecordingLogger,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
checkExpectedLogMessages,
|
||||
@@ -117,7 +117,7 @@ test.serial("convert to semver", (t) => {
|
||||
try {
|
||||
const parsedVersion = setupCodeql.convertToSemVer(
|
||||
version,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
t.deepEqual(parsedVersion, expectedVersion);
|
||||
} catch (e) {
|
||||
@@ -145,7 +145,7 @@ test.serial(
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.deepEqual(source, {
|
||||
@@ -231,7 +231,7 @@ for (const {
|
||||
GitHubVariant.DOTCOM,
|
||||
tarSupportsZstd,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
@@ -597,7 +597,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(result.toolsVersion, "local");
|
||||
@@ -634,7 +634,7 @@ for (const toolsInput of ["nightly", "nightly-latest"]) {
|
||||
GitHubVariant.DOTCOM,
|
||||
true, // tarSupportsZstd
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.deepEqual(source, {
|
||||
@@ -694,7 +694,7 @@ test.serial(
|
||||
GitHubVariant.DOTCOM,
|
||||
true, // tarSupportsZstd
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
@@ -733,7 +733,7 @@ for (const perLanguageBundles of [false, true]) {
|
||||
? [Feature.ForceNightly, Feature.PerLanguageBundles]
|
||||
: [Feature.ForceNightly],
|
||||
),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
@@ -848,7 +848,7 @@ test.serial(
|
||||
GitHubVariant.DOTCOM,
|
||||
true,
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
{
|
||||
message:
|
||||
@@ -1020,7 +1020,7 @@ test.serial(
|
||||
t.is(
|
||||
setupCodeql.tryGetTagNameFromUrl(
|
||||
"https://github.com/org/codeql-bundle-testing/releases/download/codeql-bundle-v2.19.0/codeql-bundle-linux64.tar.zst",
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
"codeql-bundle-v2.19.0",
|
||||
);
|
||||
@@ -1032,7 +1032,7 @@ test.serial(
|
||||
(t) => {
|
||||
sinon.stub(toolcache, "findAllVersions").returns([]);
|
||||
t.is(
|
||||
setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)),
|
||||
setupCodeql.getLatestToolcacheVersion(new RecordingLogger()),
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
@@ -1044,7 +1044,7 @@ test.serial(
|
||||
const testVersions = ["2.3.1", "3.2.1", "1.2.3"];
|
||||
sinon.stub(toolcache, "findAllVersions").returns(testVersions);
|
||||
|
||||
t.is(setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), "3.2.1");
|
||||
t.is(setupCodeql.getLatestToolcacheVersion(new RecordingLogger()), "3.2.1");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1070,7 +1070,7 @@ async function fakeOverlayBaseCacheKey(
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource uses overlay-aware default version when requested for a PR",
|
||||
"getCodeQLSource uses overlay-aware default version for a Code Scanning PR",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1097,8 +1097,8 @@ test.serial(
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
createFeatures([]),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(listStub.calledOnce);
|
||||
@@ -1109,7 +1109,95 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource skips overlay-aware default version when not requested",
|
||||
"getCodeQLSource skips the Actions cache when only one default version is enabled",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env[EnvVar.CODE_SCANNING_REF] = "refs/heads/feature-branch";
|
||||
process.env[EnvVar.CODE_SCANNING_BASE_BRANCH] = "main";
|
||||
|
||||
sinon.stub(api, "getAutomationID").resolves("test/");
|
||||
const listStub = sinon.stub(api, "listActionsCaches").resolves([
|
||||
{
|
||||
key: await fakeOverlayBaseCacheKey(
|
||||
"javascript",
|
||||
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
|
||||
"abc-1-1",
|
||||
),
|
||||
},
|
||||
]);
|
||||
sinon
|
||||
.stub(toolcache, "find")
|
||||
.withArgs(
|
||||
"CodeQL",
|
||||
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
|
||||
)
|
||||
.returns("/path/to/codeql");
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
undefined,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
["javascript"],
|
||||
undefined, // otherLanguagePacksReason
|
||||
true,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
createFeatures([]),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(listStub.notCalled);
|
||||
t.is(source.sourceType, "toolcache");
|
||||
t.is(
|
||||
source.toolsVersion,
|
||||
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource skips overlay-aware default version outside pull request analysis",
|
||||
async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(false);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
sinon.stub(api, "getAutomationID").resolves("test/");
|
||||
const listStub = sinon.stub(api, "listActionsCaches").resolves([
|
||||
{
|
||||
key: await fakeOverlayBaseCacheKey("javascript", "2.20.1", "abc-1-1"),
|
||||
},
|
||||
]);
|
||||
sinon
|
||||
.stub(toolcache, "find")
|
||||
.withArgs("CodeQL", "2.20.2")
|
||||
.returns("/path/to/codeql-2.20.2");
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
undefined,
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
undefined, // otherLanguagePacksReason
|
||||
true,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
createFeatures([]),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(listStub.notCalled);
|
||||
t.is(source.sourceType, "toolcache");
|
||||
t.is(source.toolsVersion, "2.20.2");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource skips overlay-aware default version when not requested for Code Scanning",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1136,8 +1224,8 @@ test.serial(
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
createFeatures([]),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(listStub.notCalled);
|
||||
@@ -1175,7 +1263,7 @@ test.serial(
|
||||
GitHubVariant.DOTCOM,
|
||||
true, // tarSupportsZstd
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
@@ -1226,7 +1314,7 @@ test.serial(
|
||||
GitHubVariant.DOTCOM,
|
||||
true, // tarSupportsZstd
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
@@ -1296,7 +1384,7 @@ for (const fallback of [false, true]) {
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.true(fetchRelease.calledTwice);
|
||||
@@ -1561,7 +1649,7 @@ for (const error of [
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.PerLanguageBundles]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
{ is: error },
|
||||
);
|
||||
@@ -1597,7 +1685,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
{ is: error },
|
||||
);
|
||||
@@ -1608,7 +1696,7 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getEnabledVersionsWithOverlayBaseDatabases returns flag-enabled versions present in cache, sorted desc",
|
||||
"getEnabledVersionsWithOverlayBaseDatabases returns enabled versions present in cache, sorted desc",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getAutomationID").resolves("test/");
|
||||
sinon.stub(api, "listActionsCaches").resolves([
|
||||
@@ -1626,21 +1714,24 @@ test.serial(
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
t.deepEqual(result, [
|
||||
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
|
||||
{ cliVersion: "2.20.0", tagName: "codeql-bundle-v2.20.0" },
|
||||
]);
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result =
|
||||
await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
new RecordingLogger(),
|
||||
);
|
||||
t.deepEqual(result, [
|
||||
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
|
||||
{ cliVersion: "2.20.0", tagName: "codeql-bundle-v2.20.0" },
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getEnabledVersionsWithOverlayBaseDatabases returns empty when no cached version is flag-enabled",
|
||||
"getEnabledVersionsWithOverlayBaseDatabases returns empty when no cached version is enabled",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getAutomationID").resolves("test/");
|
||||
sinon.stub(api, "listActionsCaches").resolves([
|
||||
@@ -1652,8 +1743,7 @@ test.serial(
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
t.deepEqual(result, []);
|
||||
},
|
||||
@@ -1669,8 +1759,7 @@ const noLanguagesMacro = makeMacro({
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
rawLanguages,
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
t.deepEqual(result, []);
|
||||
t.assert(
|
||||
@@ -1694,8 +1783,7 @@ test.serial(
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
t.deepEqual(result, []);
|
||||
},
|
||||
@@ -1714,8 +1802,7 @@ test.serial(
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersion]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
t.deepEqual(result, [
|
||||
{ cliVersion: "2.20.2", tagName: "codeql-bundle-v2.20.2" },
|
||||
@@ -1723,78 +1810,6 @@ test.serial(
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getEnabledVersionsWithOverlayBaseDatabases does not list caches when both gates are off",
|
||||
async (t) => {
|
||||
const listStub = sinon.stub(api, "listActionsCaches").resolves([]);
|
||||
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
t.deepEqual(result, []);
|
||||
t.assert(
|
||||
listStub.notCalled,
|
||||
"Should not list Actions caches when both gating feature flags are off.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getEnabledVersionsWithOverlayBaseDatabases dry-run returns empty but lists caches",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getAutomationID").resolves("test/");
|
||||
const listStub = sinon.stub(api, "listActionsCaches").resolves([
|
||||
{
|
||||
key: await fakeOverlayBaseCacheKey("javascript", "2.20.1", "abc-1-1"),
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([Feature.OverlayAnalysisMatchCodeqlVersionDryRun]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
t.deepEqual(
|
||||
result,
|
||||
[],
|
||||
"Dry-run should return an empty list so the caller falls back.",
|
||||
);
|
||||
t.assert(
|
||||
listStub.calledOnce,
|
||||
"Dry-run should still list Actions caches to populate the diagnostic.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getEnabledVersionsWithOverlayBaseDatabases match flag wins over dry-run",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getAutomationID").resolves("test/");
|
||||
sinon.stub(api, "listActionsCaches").resolves([
|
||||
{
|
||||
key: await fakeOverlayBaseCacheKey("javascript", "2.20.1", "abc-1-1"),
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await setupCodeql.getEnabledVersionsWithOverlayBaseDatabases(
|
||||
overlayMatchEnabledVersions,
|
||||
["javascript"],
|
||||
createFeatures([
|
||||
Feature.OverlayAnalysisMatchCodeqlVersion,
|
||||
Feature.OverlayAnalysisMatchCodeqlVersionDryRun,
|
||||
]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
t.deepEqual(result, [
|
||||
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
/** The CLI version that the toolcache cleanup tests download. */
|
||||
const CLEANUP_CLI_VERSION = "2.21.0";
|
||||
/** The bundle version that the toolcache cleanup tests download. */
|
||||
@@ -2093,7 +2108,7 @@ test.serial(
|
||||
env: new ReadOnlyEnv({
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: injectedRoot,
|
||||
}),
|
||||
logger: getRunnerLogger(true),
|
||||
logger: new RecordingLogger(),
|
||||
});
|
||||
|
||||
t.deepEqual(result, {
|
||||
@@ -2248,7 +2263,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(
|
||||
@@ -2288,7 +2303,7 @@ test.serial(
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
{ is: error },
|
||||
);
|
||||
@@ -2403,7 +2418,7 @@ test.serial(
|
||||
"isToolcacheOnWorkspaceFilesystem assumes a different filesystem when it cannot tell",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
t.true(toolsDownload.isToolcacheOnWorkspaceFilesystem(logger));
|
||||
|
||||
@@ -183,29 +183,16 @@ async function findOverridingToolsInCache(
|
||||
|
||||
/**
|
||||
* Returns the sorted set of enabled versions that have cached overlay-base databases for the
|
||||
* given languages, or an empty list if neither the `OverlayAnalysisMatchCodeqlVersion` nor the
|
||||
* `OverlayAnalysisMatchCodeqlVersionDryRun` feature flag is enabled. When only the dry-run flag
|
||||
* is enabled, this performs the lookup and emits a telemetry diagnostic with the version that
|
||||
* would have been chosen, but still returns an empty list so the caller falls back.
|
||||
* given languages.
|
||||
*/
|
||||
export async function getEnabledVersionsWithOverlayBaseDatabases(
|
||||
defaultCliVersion: CodeQLDefaultVersionInfo,
|
||||
rawLanguages: string[] | undefined,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<CodeQLVersionInfo[]> {
|
||||
if (rawLanguages === undefined || rawLanguages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const isEnabled = await features.getValue(
|
||||
Feature.OverlayAnalysisMatchCodeqlVersion,
|
||||
);
|
||||
const isDryRun =
|
||||
!isEnabled &&
|
||||
(await features.getValue(Feature.OverlayAnalysisMatchCodeqlVersionDryRun));
|
||||
if (!isEnabled && !isDryRun) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let cachedVersions: string[] | undefined;
|
||||
try {
|
||||
@@ -226,66 +213,35 @@ export async function getEnabledVersionsWithOverlayBaseDatabases(
|
||||
}
|
||||
|
||||
const cachedVersionsSet = new Set(cachedVersions);
|
||||
const overlayVersions = defaultCliVersion.enabledVersions.filter((v) =>
|
||||
return defaultCliVersion.enabledVersions.filter((v) =>
|
||||
cachedVersionsSet.has(v.cliVersion),
|
||||
);
|
||||
|
||||
if (overlayVersions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const isCachedVersionDifferent =
|
||||
overlayVersions[0].cliVersion !==
|
||||
defaultCliVersion.enabledVersions[0].cliVersion;
|
||||
|
||||
if (isCachedVersionDifferent) {
|
||||
addNoLanguageDiagnostic(
|
||||
undefined,
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/overlay-aware-default-codeql-version",
|
||||
"Overlay-aware default CodeQL version selection",
|
||||
{
|
||||
cachedVersions,
|
||||
enabledVersions: defaultCliVersion.enabledVersions.map(
|
||||
(v) => v.cliVersion,
|
||||
),
|
||||
isDryRun,
|
||||
overlayAwareVersion: overlayVersions[0].cliVersion,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (isDryRun) {
|
||||
logger.debug(
|
||||
`Overlay-aware default CodeQL version selection is running in dry-run mode. Would have used version ${overlayVersions[0].cliVersion}.`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return overlayVersions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the newest enabled default CLI version that has a cached overlay-base database for the
|
||||
* relevant languages, if running a Code Scanning analysis for a pull request and one exists.
|
||||
* Otherwise, falls back to the newest enabled default CLI version.
|
||||
* relevant languages, if this is a Code Scanning analysis of a pull request, multiple default
|
||||
* versions are enabled, and such a version exists. Otherwise, falls back to the newest enabled
|
||||
* default CLI version.
|
||||
*/
|
||||
async function resolveDefaultCliVersion(
|
||||
defaultCliVersion: CodeQLDefaultVersionInfo,
|
||||
rawLanguages: string[] | undefined,
|
||||
useOverlayAwareDefaultCliVersion: boolean,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<CodeQLVersionInfo> {
|
||||
if (!useOverlayAwareDefaultCliVersion || !isAnalyzingPullRequest()) {
|
||||
if (
|
||||
!useOverlayAwareDefaultCliVersion ||
|
||||
// Listing the Actions cache can't change which version we use if only one version is enabled.
|
||||
defaultCliVersion.enabledVersions.length === 1 ||
|
||||
!isAnalyzingPullRequest()
|
||||
) {
|
||||
return defaultCliVersion.enabledVersions[0];
|
||||
}
|
||||
|
||||
const overlayVersions = await getEnabledVersionsWithOverlayBaseDatabases(
|
||||
defaultCliVersion,
|
||||
rawLanguages,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
if (overlayVersions.length > 0) {
|
||||
@@ -482,7 +438,6 @@ export async function getCodeQLSource(
|
||||
defaultCliVersion,
|
||||
rawLanguages,
|
||||
useOverlayAwareDefaultCliVersion,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
cliVersion = version.cliVersion;
|
||||
@@ -505,7 +460,6 @@ export async function getCodeQLSource(
|
||||
defaultCliVersion,
|
||||
rawLanguages,
|
||||
useOverlayAwareDefaultCliVersion,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
cliVersion = version.cliVersion;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { setUpFeatureFlagTests } from "./feature-flags/testing-util";
|
||||
import { UnvalidatedObject, validateSchema } from "./json";
|
||||
import { makeFromSchema } from "./json/testing-util";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import { Logger } from "./logging";
|
||||
import { BundlePlatform, getBundlePlatform } from "./platform";
|
||||
import * as repository from "./setup/repository";
|
||||
import * as startProxyExports from "./start-proxy";
|
||||
@@ -67,7 +67,7 @@ test("getCredentials prefers registriesCredentials over registrySecrets", async
|
||||
]);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
registrySecrets,
|
||||
registryCredentials,
|
||||
undefined,
|
||||
@@ -84,7 +84,7 @@ test("getCredentials throws an error when configurations are not an array", asyn
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
registryCredentials,
|
||||
undefined,
|
||||
@@ -103,7 +103,7 @@ test("getCredentials throws error when credential is not an object", async (t) =
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
@@ -124,7 +124,7 @@ test("getCredentials throws error when credential is missing type", async (t) =>
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
@@ -147,7 +147,7 @@ test("getCredentials throws error when credential missing host and url", async (
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
@@ -161,7 +161,7 @@ test("getCredentials throws error when credential missing host and url", async (
|
||||
|
||||
test("getCredentials filters by language when specified", async (t) => {
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON(mixedCredentials),
|
||||
BuiltInLanguage.java,
|
||||
@@ -172,7 +172,7 @@ test("getCredentials filters by language when specified", async (t) => {
|
||||
|
||||
test("getCredentials returns all for a language when specified", async (t) => {
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON([...mixedCredentials, gitSourceCredential]),
|
||||
BuiltInLanguage.go,
|
||||
@@ -192,7 +192,7 @@ test("getCredentials returns all goproxy_servers for Go when specified", async (
|
||||
];
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON(multipleGoproxyServers),
|
||||
BuiltInLanguage.go,
|
||||
@@ -221,7 +221,7 @@ test("getCredentials returns all maven_repositories for Java when specified", as
|
||||
];
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON(multipleMavenRepositories),
|
||||
BuiltInLanguage.java,
|
||||
@@ -239,7 +239,7 @@ test("getCredentials returns all credentials when no language specified", async
|
||||
const credentialsInput = toEncodedJSON(mixedCredentials);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
@@ -269,7 +269,7 @@ test("getCredentials throws an error when non-printable characters are used", as
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
@@ -300,7 +300,7 @@ for (const oidcSchemaInfo of startProxyExports.oidcSchemas) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
@@ -324,7 +324,7 @@ test("getCredentials accepts OIDC configurations", (t) => {
|
||||
);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON(oidcConfigurations),
|
||||
BuiltInLanguage.csharp,
|
||||
@@ -514,7 +514,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
]);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
BuiltInLanguage.java,
|
||||
@@ -533,7 +533,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
};
|
||||
t.throws(() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON([{ ...baseInvalid, "replaces-base": null }]),
|
||||
BuiltInLanguage.java,
|
||||
@@ -541,7 +541,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
);
|
||||
t.throws(() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON([{ ...baseInvalid, "replaces-base": 123 }]),
|
||||
BuiltInLanguage.java,
|
||||
@@ -549,7 +549,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
);
|
||||
t.throws(() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
toEncodedJSON([{ ...baseInvalid, "replaces-base": "true" }]),
|
||||
BuiltInLanguage.java,
|
||||
@@ -565,7 +565,7 @@ test("getCredentials returns only ALWAYS_ENABLED_REGISTRY_TYPE credentials for A
|
||||
]);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
BuiltInLanguage.actions,
|
||||
@@ -597,7 +597,7 @@ test("getCredentials always returns ALWAYS_ENABLED_REGISTRY_TYPE credentials for
|
||||
// Test all languages.
|
||||
for (const language of Object.values(BuiltInLanguage)) {
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
language,
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as actionsUtil from "./actions-util";
|
||||
import { Config } from "./config-utils";
|
||||
import { EnvVar, RegistryProxyVars } from "./environment";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { ToolsSource } from "./setup-codeql";
|
||||
import type { Registry } from "./start-proxy";
|
||||
import {
|
||||
@@ -154,7 +153,7 @@ test.serial("createStatusReportBase", async (t) => {
|
||||
languages: [BuiltInLanguage.java, BuiltInLanguage.swift],
|
||||
}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
);
|
||||
@@ -203,7 +202,7 @@ test.serial("createStatusReportBase - empty configuration", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
{},
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
if (t.truthy(statusReport)) {
|
||||
@@ -225,7 +224,7 @@ test.serial("createStatusReportBase - partial configuration", async (t) => {
|
||||
languages: ["go"],
|
||||
},
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
if (t.truthy(statusReport)) {
|
||||
@@ -248,7 +247,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
)
|
||||
@@ -264,7 +263,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
)
|
||||
@@ -281,7 +280,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
)
|
||||
@@ -297,7 +296,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
)
|
||||
@@ -314,7 +313,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
)
|
||||
@@ -330,7 +329,7 @@ test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
)
|
||||
@@ -411,7 +410,7 @@ const testCreateInitWithConfigStatusReport = makeMacro({
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
config,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
new RecordingLogger(),
|
||||
"failure cause",
|
||||
"exception stack trace",
|
||||
);
|
||||
|
||||
@@ -3,9 +3,8 @@ import * as stream from "stream";
|
||||
|
||||
import test from "ava";
|
||||
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { extractTarZst } from "./tar";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { RecordingLogger, setupTests } from "./testing-utils";
|
||||
import { withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -17,7 +16,7 @@ test("extractTarZst rejects if the input stream errors", async (t) => {
|
||||
archive,
|
||||
path.join(tmpDir, "dest"),
|
||||
{ type: "gnu", version: "1.34" },
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
archive.destroy(
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
featureConfig,
|
||||
FeatureEnablement,
|
||||
} from "./feature-flags";
|
||||
import { Logger } from "./logging";
|
||||
import { joinMessageStrings, Loggable, LoggableError, Logger } from "./logging";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import { getBundlePlatform } from "./platform";
|
||||
import { ActionName } from "./status-report";
|
||||
@@ -675,12 +675,13 @@ export class RecordingLogger implements Logger {
|
||||
|
||||
constructor(private readonly logToConsole: boolean = true) {}
|
||||
|
||||
private addMessage(level: LogLevel, message: string | Error): void {
|
||||
this.messages.push({ type: level, message });
|
||||
private addMessage(level: LogLevel, message: LoggableError): void {
|
||||
const joinedMessage = joinMessageStrings(message);
|
||||
this.messages.push({ type: level, message: joinedMessage });
|
||||
|
||||
if (this.logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message);
|
||||
console.debug(joinedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,19 +702,19 @@ export class RecordingLogger implements Logger {
|
||||
return true;
|
||||
}
|
||||
|
||||
debug(message: string) {
|
||||
debug(message: Loggable) {
|
||||
this.addMessage("debug", message);
|
||||
}
|
||||
|
||||
info(message: string) {
|
||||
info(message: Loggable) {
|
||||
this.addMessage("info", message);
|
||||
}
|
||||
|
||||
warning(message: string | Error) {
|
||||
warning(message: LoggableError) {
|
||||
this.addMessage("warning", message);
|
||||
}
|
||||
|
||||
error(message: string | Error) {
|
||||
error(message: LoggableError) {
|
||||
this.addMessage("error", message);
|
||||
}
|
||||
|
||||
@@ -1048,6 +1049,7 @@ export function createTestConfig(overrides: Partial<Config>): Config {
|
||||
overlayModeSetExplicitly: false,
|
||||
repositoryProperties: {},
|
||||
enableFileCoverageInformation: true,
|
||||
successfullyInitialised: false,
|
||||
} satisfies Config,
|
||||
overrides,
|
||||
);
|
||||
@@ -1058,3 +1060,15 @@ export function makeTestToken(length: number = 36) {
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
return chars.repeat(Math.ceil(length / chars.length)).slice(0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a real, newly created CodeQL instance for testing. The instance refers to
|
||||
* a non-existent placeholder codeql command, so tests that use this function
|
||||
* should also stub the toolrunner.ToolRunner constructor.
|
||||
*/
|
||||
export async function getCodeQLForTesting(
|
||||
cmd = "codeql-for-testing",
|
||||
logger: Logger = new RecordingLogger(),
|
||||
): Promise<codeql.CodeQL> {
|
||||
return codeql.getCodeQLForCmd(logger, cmd, false);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ import { https } from "follow-redirects";
|
||||
import nock from "nock";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import * as tar from "./tar";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { RecordingLogger, setupTests } from "./testing-utils";
|
||||
import { downloadAndExtract } from "./tools-download";
|
||||
import * as util from "./util";
|
||||
|
||||
@@ -34,7 +33,7 @@ test.serial(
|
||||
undefined,
|
||||
{},
|
||||
undefined,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(Number.isInteger(statusReport.downloadDurationMs));
|
||||
@@ -72,7 +71,7 @@ test.serial(
|
||||
undefined,
|
||||
{},
|
||||
{ type: "gnu", version: "1.34" },
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(Number.isInteger(statusReport.downloadDurationMs));
|
||||
@@ -105,7 +104,7 @@ test.serial(
|
||||
undefined,
|
||||
{},
|
||||
{ type: "gnu", version: "1.34" },
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
{
|
||||
instanceOf: util.HTTPError,
|
||||
@@ -149,7 +148,7 @@ test.serial(
|
||||
undefined,
|
||||
{},
|
||||
{ type: "gnu", version: "1.34" },
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(Number.isInteger(statusReport.downloadDurationMs));
|
||||
@@ -189,7 +188,7 @@ test.serial(
|
||||
undefined,
|
||||
{},
|
||||
{ type: "gnu", version: "1.34" },
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.is(
|
||||
@@ -237,7 +236,7 @@ test.serial(
|
||||
undefined,
|
||||
{},
|
||||
{ type: "gnu", version: "1.34" },
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
);
|
||||
|
||||
t.assert(Number.isInteger(statusReport.totalDurationMs));
|
||||
|
||||
@@ -4,11 +4,12 @@ import * as path from "path";
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { CodeQL, getCodeQLForTesting } from "./codeql";
|
||||
import type { CodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import {
|
||||
createTestConfig,
|
||||
getCodeQLForTesting,
|
||||
makeVersionInfo,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
|
||||
@@ -6,29 +6,26 @@ import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as apiClient from "./api-client";
|
||||
import {
|
||||
getTrapCachingExtractorConfigArgs,
|
||||
getTrapCachingExtractorConfigArgsForLang,
|
||||
createStubCodeQL,
|
||||
} from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
createFeatures,
|
||||
callee,
|
||||
createTestConfig,
|
||||
getRecordingLogger,
|
||||
makeVersionInfo,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import {
|
||||
cleanupTrapCaches,
|
||||
downloadTrapCaches,
|
||||
getLanguagesSupportingCaching,
|
||||
uploadTrapCaches,
|
||||
warnIfTrapCacheCleanupEnvVarIsSet,
|
||||
} from "./trap-caching";
|
||||
import * as util from "./util";
|
||||
|
||||
@@ -200,83 +197,27 @@ test.serial(
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("cleanup removes only old CodeQL TRAP caches", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
// This config specifies that we are analyzing JavaScript and Ruby, but not Swift.
|
||||
const config = getTestConfigWithTempDir(tmpDir);
|
||||
|
||||
sinon.stub(gitUtils, "getRef").resolves("refs/heads/main");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
const listStub = sinon.stub(apiClient, "listActionsCaches").resolves([
|
||||
// Should be kept, since it's not relevant to CodeQL. In reality, the API shouldn't return
|
||||
// this in the first place, but this is a defensive check.
|
||||
{
|
||||
id: 1,
|
||||
key: "some-other-key",
|
||||
created_at: "2024-05-23T14:25:00Z",
|
||||
size_in_bytes: 100 * 1024 * 1024,
|
||||
},
|
||||
// Should be kept, since it's the newest TRAP cache for JavaScript
|
||||
{
|
||||
id: 2,
|
||||
key: "codeql-trap-1-2.0.0-javascript-newest",
|
||||
created_at: "2024-04-23T14:25:00Z",
|
||||
size_in_bytes: 50 * 1024 * 1024,
|
||||
},
|
||||
// Should be cleaned up
|
||||
{
|
||||
id: 3,
|
||||
key: "codeql-trap-1-2.0.0-javascript-older",
|
||||
created_at: "2024-03-22T14:25:00Z",
|
||||
size_in_bytes: 200 * 1024 * 1024,
|
||||
},
|
||||
// Should be cleaned up
|
||||
{
|
||||
id: 4,
|
||||
key: "codeql-trap-1-2.0.0-javascript-oldest",
|
||||
created_at: "2024-02-21T14:25:00Z",
|
||||
size_in_bytes: 300 * 1024 * 1024,
|
||||
},
|
||||
// Should be kept, since it's the newest TRAP cache for Ruby
|
||||
{
|
||||
id: 5,
|
||||
key: "codeql-trap-1-2.0.0-ruby-newest",
|
||||
created_at: "2024-02-20T14:25:00Z",
|
||||
size_in_bytes: 300 * 1024 * 1024,
|
||||
},
|
||||
// Should be kept, since we aren't analyzing Swift
|
||||
{
|
||||
id: 6,
|
||||
key: "codeql-trap-1-2.0.0-swift-newest",
|
||||
created_at: "2024-02-22T14:25:00Z",
|
||||
size_in_bytes: 300 * 1024 * 1024,
|
||||
},
|
||||
// Should be kept, since we aren't analyzing Swift
|
||||
{
|
||||
id: 7,
|
||||
key: "codeql-trap-1-2.0.0-swift-older",
|
||||
created_at: "2024-02-21T14:25:00Z",
|
||||
size_in_bytes: 300 * 1024 * 1024,
|
||||
},
|
||||
]);
|
||||
|
||||
const deleteStub = sinon.stub(apiClient, "deleteActionsCache").resolves();
|
||||
|
||||
const statusReport = await cleanupTrapCaches(
|
||||
config,
|
||||
createFeatures([Feature.CleanupTrapCaches]),
|
||||
getRunnerLogger(true),
|
||||
// `CODEQL_ACTION_CLEANUP_TRAP_CACHES` has no effect with either value, so we warn even when it's
|
||||
// set to `false`.
|
||||
for (const value of ["true", "false"]) {
|
||||
test(`warnIfTrapCacheCleanupEnvVarIsSet warns when CODEQL_ACTION_CLEANUP_TRAP_CACHES is '${value}'`, async (t) => {
|
||||
const target = callee(warnIfTrapCacheCleanupEnvVarIsSet)
|
||||
.withEnv((env) => env.set("CODEQL_ACTION_CLEANUP_TRAP_CACHES", value))
|
||||
.withArgs()
|
||||
.logs(
|
||||
t,
|
||||
"CODEQL_ACTION_CLEANUP_TRAP_CACHES environment variable no longer has any effect",
|
||||
);
|
||||
await target.passes(t.is, undefined);
|
||||
t.deepEqual(
|
||||
target.getLogger().messages.map((m) => m.type),
|
||||
["warning"],
|
||||
);
|
||||
|
||||
t.is(listStub.callCount, 1);
|
||||
t.assert(listStub.calledWithExactly("codeql-trap", "refs/heads/main"));
|
||||
|
||||
t.deepEqual(statusReport, {
|
||||
trap_cache_cleanup_size_bytes: 500 * 1024 * 1024,
|
||||
});
|
||||
|
||||
t.is(deleteStub.callCount, 2);
|
||||
t.assert(deleteStub.calledWithExactly(3));
|
||||
t.assert(deleteStub.calledWithExactly(4));
|
||||
});
|
||||
}
|
||||
|
||||
test("warnIfTrapCacheCleanupEnvVarIsSet doesn't warn when CODEQL_ACTION_CLEANUP_TRAP_CACHES is unset", async (t) => {
|
||||
const target = callee(warnIfTrapCacheCleanupEnvVarIsSet).withArgs();
|
||||
await target.passes(t.is, undefined);
|
||||
t.deepEqual(target.getLogger().messages, []);
|
||||
});
|
||||
|
||||
@@ -3,22 +3,14 @@ import * as path from "path";
|
||||
|
||||
import * as actionsCache from "@actions/cache";
|
||||
|
||||
import { type ActionState } from "./action-common";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as apiClient from "./api-client";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { type Config } from "./config-utils";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { Feature, FeatureEnablement } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
asHTTPError,
|
||||
getEnv,
|
||||
getErrorMessage,
|
||||
tryGetFolderBytes,
|
||||
waitForResultWithTimeLimit,
|
||||
} from "./util";
|
||||
import { getEnv, tryGetFolderBytes, waitForResultWithTimeLimit } from "./util";
|
||||
|
||||
// This constant should be bumped if we make a breaking change
|
||||
// to how the CodeQL Action stores or retrieves the TRAP cache,
|
||||
@@ -178,113 +170,25 @@ export async function uploadTrapCaches(
|
||||
return true;
|
||||
}
|
||||
|
||||
export interface TrapCacheCleanupStatusReport {
|
||||
trap_cache_cleanup_error?: string;
|
||||
trap_cache_cleanup_size_bytes?: number;
|
||||
trap_cache_cleanup_skipped_because?: string;
|
||||
}
|
||||
|
||||
export async function cleanupTrapCaches(
|
||||
config: Config,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<TrapCacheCleanupStatusReport> {
|
||||
const env = getEnv();
|
||||
|
||||
if (!(await features.getValue(Feature.CleanupTrapCaches))) {
|
||||
return {
|
||||
trap_cache_cleanup_skipped_because: "feature disabled",
|
||||
};
|
||||
}
|
||||
logger.warning(
|
||||
"TRAP cache cleanup is deprecated and will be removed in May 2026. " +
|
||||
"We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action.",
|
||||
);
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot))) {
|
||||
return {
|
||||
trap_cache_cleanup_skipped_because: "not analyzing default branch",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
let totalBytesCleanedUp = 0;
|
||||
|
||||
const allCaches = await apiClient.listActionsCaches(
|
||||
CODEQL_TRAP_CACHE_PREFIX,
|
||||
await gitUtils.getRef(env, config.repositoryRoot),
|
||||
/**
|
||||
* Warns if the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable is set to a non-empty
|
||||
* value. That variable controlled the removed TRAP cache cleanup feature, so setting it has no
|
||||
* effect.
|
||||
*/
|
||||
export function warnIfTrapCacheCleanupEnvVarIsSet({
|
||||
env,
|
||||
logger,
|
||||
}: ActionState<["Logger", "ReadOnlyEnv"]>): void {
|
||||
if (env.getOptional("CODEQL_ACTION_CLEANUP_TRAP_CACHES") !== undefined) {
|
||||
logger.warning(
|
||||
"TRAP cache cleanup has been removed, so the CODEQL_ACTION_CLEANUP_TRAP_CACHES environment " +
|
||||
"variable no longer has any effect and can be removed from your workflow. If you used TRAP " +
|
||||
"cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching instead by " +
|
||||
"passing the `trap-caching: false` input to the `init` Action.",
|
||||
);
|
||||
|
||||
for (const language of config.languages) {
|
||||
if (config.trapCaches[language]) {
|
||||
const cachesToRemove = await getTrapCachesForLanguage(
|
||||
allCaches,
|
||||
language,
|
||||
logger,
|
||||
);
|
||||
// Dates returned by the API are in ISO 8601 format, so we can sort them lexicographically
|
||||
cachesToRemove.sort((a, b) => a.created_at.localeCompare(b.created_at));
|
||||
// Keep the most recent cache
|
||||
const mostRecentCache = cachesToRemove.pop();
|
||||
logger.debug(
|
||||
`Keeping most recent TRAP cache (${JSON.stringify(mostRecentCache)})`,
|
||||
);
|
||||
|
||||
if (cachesToRemove.length === 0) {
|
||||
logger.info(`No TRAP caches to clean up for ${language}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const cache of cachesToRemove) {
|
||||
logger.debug(`Cleaning up TRAP cache (${JSON.stringify(cache)})`);
|
||||
await apiClient.deleteActionsCache(cache.id);
|
||||
}
|
||||
const bytesCleanedUp = cachesToRemove.reduce(
|
||||
(acc, item) => acc + item.size_in_bytes,
|
||||
0,
|
||||
);
|
||||
totalBytesCleanedUp += bytesCleanedUp;
|
||||
const megabytesCleanedUp = (bytesCleanedUp / (1024 * 1024)).toFixed(2);
|
||||
logger.info(
|
||||
`Cleaned up ${megabytesCleanedUp} MiB of old TRAP caches for ${language}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return { trap_cache_cleanup_size_bytes: totalBytesCleanedUp };
|
||||
} catch (e) {
|
||||
if (asHTTPError(e)?.status === 403) {
|
||||
logger.warning(
|
||||
"Could not cleanup TRAP caches as the token did not have the required permissions. " +
|
||||
'To clean up TRAP caches, ensure the token has the "actions:write" permission. ' +
|
||||
`See ${DocUrl.ASSIGNING_PERMISSIONS_TO_JOBS} for more information.`,
|
||||
);
|
||||
} else {
|
||||
logger.info(`Failed to cleanup TRAP caches, continuing. Details: ${e}`);
|
||||
}
|
||||
return { trap_cache_cleanup_error: getErrorMessage(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrapCachesForLanguage(
|
||||
allCaches: apiClient.ActionsCacheItem[],
|
||||
language: Language,
|
||||
logger: Logger,
|
||||
): Promise<Array<Required<apiClient.ActionsCacheItem>>> {
|
||||
logger.debug(`Listing TRAP caches for ${language}`);
|
||||
|
||||
for (const cache of allCaches) {
|
||||
if (!cache.created_at || !cache.id || !cache.key || !cache.size_in_bytes) {
|
||||
throw new Error(
|
||||
"An unexpected cache item was returned from the API that was missing one or " +
|
||||
`more required fields: ${JSON.stringify(cache)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return allCaches.filter((cache) => {
|
||||
return cache.key?.includes(`-${language}-`);
|
||||
}) as Array<Required<apiClient.ActionsCacheItem>>;
|
||||
}
|
||||
|
||||
export async function getLanguagesSupportingCaching(
|
||||
codeql: CodeQL,
|
||||
languages: Language[],
|
||||
|
||||
@@ -10,9 +10,9 @@ import * as analyses from "./analyses";
|
||||
import { AnalysisKind, CodeQuality, CodeScanning } from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import * as diffUtils from "./diff-informed-analysis-utils";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import { Logger } from "./logging";
|
||||
import * as sarif from "./sarif";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { RecordingLogger, setupTests } from "./testing-utils";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import { UploadPayload } from "./upload-lib/types";
|
||||
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
|
||||
@@ -29,7 +29,7 @@ test.serial("validateSarifFileSchema - valid", (t) => {
|
||||
uploadLib.validateSarifFileSchema(
|
||||
uploadLib.readSarifFileOrThrow(inputFile),
|
||||
inputFile,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -40,7 +40,7 @@ test.serial("validateSarifFileSchema - invalid", (t) => {
|
||||
uploadLib.validateSarifFileSchema(
|
||||
uploadLib.readSarifFileOrThrow(inputFile),
|
||||
inputFile,
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -179,7 +179,7 @@ test.serial("finding SARIF files", async (t) => {
|
||||
}
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
tmpDir,
|
||||
);
|
||||
|
||||
@@ -200,7 +200,7 @@ test.serial("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
sarifPath,
|
||||
);
|
||||
|
||||
@@ -218,7 +218,7 @@ test.serial("getGroupedSarifFilePaths - Code Quality file", async (t) => {
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
sarifPath,
|
||||
);
|
||||
|
||||
@@ -236,7 +236,7 @@ test.serial("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
sarifPath,
|
||||
);
|
||||
|
||||
@@ -254,7 +254,7 @@ test.serial("getGroupedSarifFilePaths - Other file", async (t) => {
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
sarifPath,
|
||||
);
|
||||
|
||||
@@ -932,7 +932,7 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
|
||||
owner: mockData.owner,
|
||||
repo: mockData.repo,
|
||||
},
|
||||
getRunnerLogger(true),
|
||||
new RecordingLogger(),
|
||||
analysis,
|
||||
);
|
||||
|
||||
@@ -1019,7 +1019,7 @@ function runFilterAlertsByDiffRange(
|
||||
diffRanges: diffUtils.DiffThunkRange[],
|
||||
): Partial<sarif.Log> {
|
||||
sinon.stub(diffUtils, "readDiffRangesJsonFile").returns(diffRanges);
|
||||
return uploadLib.filterAlertsByDiffRange(getRunnerLogger(true), input);
|
||||
return uploadLib.filterAlertsByDiffRange(new RecordingLogger(), input);
|
||||
}
|
||||
|
||||
test.serial(
|
||||
|
||||
@@ -5,8 +5,12 @@ import test, { ExecutionContext } from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { AnalysisKind, getAnalysisConfig } from "./analyses";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { createFeatures, makeMacro, setupTests } from "./testing-utils";
|
||||
import {
|
||||
createFeatures,
|
||||
makeMacro,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import { UploadResult } from "./upload-lib";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import { postProcessAndUploadSarif } from "./upload-sarif";
|
||||
@@ -52,7 +56,7 @@ const postProcessAndUploadSarifMacro = makeMacro({
|
||||
expectedResult: Partial<Record<AnalysisKind, UploadSarifExpectedResult>>,
|
||||
) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const testPath = sarifPath(tempDir);
|
||||
const features = createFeatures([]);
|
||||
|
||||
@@ -210,7 +214,7 @@ test.serial(
|
||||
"postProcessAndUploadSarif doesn't upload if upload is disabled",
|
||||
async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const features = createFeatures([]);
|
||||
|
||||
const toFullPath = (filename: string) => path.join(tempDir, filename);
|
||||
@@ -244,7 +248,7 @@ test.serial(
|
||||
"postProcessAndUploadSarif writes post-processed SARIF files if output directory is provided",
|
||||
async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const logger = new RecordingLogger();
|
||||
const features = createFeatures([]);
|
||||
|
||||
const toFullPath = (filename: string) => path.join(tempDir, filename);
|
||||
|
||||
@@ -10,8 +10,7 @@ import * as sinon from "sinon";
|
||||
|
||||
import * as api from "./api-client";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
import { RecordingLogger, setupTests, getTestEnv } from "./testing-utils";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -92,7 +91,7 @@ for (const {
|
||||
|
||||
test("getMemoryFlag() throws if the ram input is < 0 or NaN", async (t) => {
|
||||
for (const input of ["-1", "hello!"]) {
|
||||
t.throws(() => util.getMemoryFlag(input, getRunnerLogger(true)));
|
||||
t.throws(() => util.getMemoryFlag(input, new RecordingLogger()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,13 +108,13 @@ test("getThreadsFlag() should return the correct --threads flag", (t) => {
|
||||
];
|
||||
|
||||
for (const [input, expectedFlag] of tests) {
|
||||
const flag = util.getThreadsFlag(input, getRunnerLogger(true));
|
||||
const flag = util.getThreadsFlag(input, new RecordingLogger());
|
||||
t.deepEqual(flag, expectedFlag);
|
||||
}
|
||||
});
|
||||
|
||||
test("getThreadsFlag() throws if the threads input is not an integer", (t) => {
|
||||
t.throws(() => util.getThreadsFlag("hello!", getRunnerLogger(true)));
|
||||
t.throws(() => util.getThreadsFlag("hello!", new RecordingLogger()));
|
||||
});
|
||||
|
||||
test.serial(
|
||||
@@ -385,7 +384,7 @@ test("getCgroupCpuCountFromCpus calculates the number of CPUs correctly", async
|
||||
const testCpuFile = `${tmpDir}/cpus-file`;
|
||||
fs.writeFileSync(testCpuFile, "1, 9-10\n", "utf-8");
|
||||
t.deepEqual(
|
||||
util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)),
|
||||
util.getCgroupCpuCountFromCpus(testCpuFile, new RecordingLogger()),
|
||||
3,
|
||||
);
|
||||
});
|
||||
@@ -396,7 +395,7 @@ test("getCgroupCpuCountFromCpus returns undefined if the CPU file doesn't exist"
|
||||
const testCpuFile = `${tmpDir}/cpus-file`;
|
||||
t.false(fs.existsSync(testCpuFile));
|
||||
t.deepEqual(
|
||||
util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)),
|
||||
util.getCgroupCpuCountFromCpus(testCpuFile, new RecordingLogger()),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
@@ -407,7 +406,7 @@ test("getCgroupCpuCountFromCpus returns undefined if the CPU file exists but is
|
||||
const testCpuFile = `${tmpDir}/cpus-file`;
|
||||
fs.writeFileSync(testCpuFile, "\n", "utf-8");
|
||||
t.deepEqual(
|
||||
util.getCgroupCpuCountFromCpus(testCpuFile, getRunnerLogger(true)),
|
||||
util.getCgroupCpuCountFromCpus(testCpuFile, new RecordingLogger()),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
@@ -417,7 +416,7 @@ test.serial(
|
||||
"checkDiskUsage succeeds and produces positive numbers",
|
||||
async (t) => {
|
||||
process.env["GITHUB_WORKSPACE"] = os.tmpdir();
|
||||
const diskUsage = await util.checkDiskUsage(getRunnerLogger(true));
|
||||
const diskUsage = await util.checkDiskUsage(new RecordingLogger());
|
||||
if (t.truthy(diskUsage)) {
|
||||
t.true(diskUsage.numAvailableBytes > 0);
|
||||
t.true(diskUsage.numTotalBytes > 0);
|
||||
|
||||
15
src/util.ts
15
src/util.ts
@@ -900,6 +900,21 @@ export function getErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approximates whether {@link error} is like a {@link NodeJS.ErrnoException}.
|
||||
* We only typically care about the `code` property, so that's what we check for here.
|
||||
*/
|
||||
export function isErrnoException(
|
||||
error: unknown,
|
||||
): error is NodeJS.ErrnoException {
|
||||
return (
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
"code" in error &&
|
||||
typeof error.code === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function prettyPrintPack(pack: Pack) {
|
||||
return `${pack.name}${pack.version ? `@${pack.version}` : ""}${
|
||||
pack.path ? `:${pack.path}` : ""
|
||||
|
||||
@@ -3,10 +3,11 @@ import * as yaml from "js-yaml";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { createStubCodeQL, getCodeQLForTesting } from "./codeql";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getCodeQLForTesting,
|
||||
createTestConfig,
|
||||
getRecordingLogger,
|
||||
getTestEnv,
|
||||
|
||||
1
unreleased-change-notes/remove-trap-cache-cleanup.md
Normal file
1
unreleased-change-notes/remove-trap-cache-cleanup.md
Normal file
@@ -0,0 +1 @@
|
||||
- The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable has been removed, as announced in the release notes for CodeQL Action 4.35.2. The `analyze` Action now warns if this environment variable is set. If you used TRAP cache cleanup to limit Actions cache usage, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#4243](https://github.com/github/codeql-action/pull/4243)
|
||||
Reference in New Issue
Block a user