Skip to content

Commit 0545bcf

Browse files
authored
Merge pull request github#6028 from github/tamasvajk/feature/csv-coverage-report-comment
Add CSV coverage PR commenter
2 parents 64001cc + 07b83d5 commit 0545bcf

File tree

5 files changed

+279
-69
lines changed

5 files changed

+279
-69
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Check framework coverage changes
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/csv-coverage-pr-comment.yml'
7+
- '*/ql/src/**/*.ql'
8+
- '*/ql/src/**/*.qll'
9+
- 'misc/scripts/library-coverage/*.py'
10+
# input data files
11+
- '*/documentation/library-coverage/cwe-sink.csv'
12+
- '*/documentation/library-coverage/frameworks.csv'
13+
branches:
14+
- main
15+
- 'rc/*'
16+
17+
jobs:
18+
generate:
19+
name: Generate framework coverage artifacts
20+
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- name: Dump GitHub context
25+
env:
26+
GITHUB_CONTEXT: ${{ toJSON(github.event) }}
27+
run: echo "$GITHUB_CONTEXT"
28+
- name: Clone self (github/codeql) - MERGE
29+
uses: actions/checkout@v2
30+
with:
31+
path: merge
32+
- name: Clone self (github/codeql) - BASE
33+
uses: actions/checkout@v2
34+
with:
35+
ref: ${{ github.event.pull_request.base.sha }}
36+
path: base
37+
- name: Set up Python 3.8
38+
uses: actions/setup-python@v2
39+
with:
40+
python-version: 3.8
41+
- name: Download CodeQL CLI
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
run: |
45+
gh release download --repo "github/codeql-cli-binaries" --pattern "codeql-linux64.zip"
46+
- name: Unzip CodeQL CLI
47+
run: unzip -d codeql-cli codeql-linux64.zip
48+
- name: Generate CSV files on merge and base of the PR
49+
run: |
50+
echo "Running generator on ${{github.sha}}"
51+
PATH="$PATH:codeql-cli/codeql" python merge/misc/scripts/library-coverage/generate-report.py ci merge merge
52+
mkdir out_merge
53+
cp framework-coverage-*.csv out_merge/
54+
cp framework-coverage-*.rst out_merge/
55+
56+
echo "Running generator on ${{github.event.pull_request.base.sha}}"
57+
PATH="$PATH:codeql-cli/codeql" python base/misc/scripts/library-coverage/generate-report.py ci base base
58+
mkdir out_base
59+
cp framework-coverage-*.csv out_base/
60+
cp framework-coverage-*.rst out_base/
61+
- name: Upload CSV package list
62+
uses: actions/upload-artifact@v2
63+
with:
64+
name: csv-framework-coverage-merge
65+
path: |
66+
out_merge/framework-coverage-*.csv
67+
out_merge/framework-coverage-*.rst
68+
- name: Upload CSV package list
69+
uses: actions/upload-artifact@v2
70+
with:
71+
name: csv-framework-coverage-base
72+
path: |
73+
out_base/framework-coverage-*.csv
74+
out_base/framework-coverage-*.rst
75+
- name: Save PR number
76+
run: |
77+
mkdir -p pr
78+
echo ${{ github.event.pull_request.number }} > pr/NR
79+
- name: Upload PR number
80+
uses: actions/upload-artifact@v2
81+
with:
82+
name: pr
83+
path: pr/
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Comment on PR with framework coverage changes
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Check framework coverage changes"]
6+
types:
7+
- completed
8+
9+
jobs:
10+
check:
11+
name: Check framework coverage differences and comment
12+
runs-on: ubuntu-latest
13+
if: >
14+
${{ github.event.workflow_run.event == 'pull_request' &&
15+
github.event.workflow_run.conclusion == 'success' }}
16+
17+
steps:
18+
- name: Dump GitHub context
19+
env:
20+
GITHUB_CONTEXT: ${{ toJSON(github.event) }}
21+
run: echo "$GITHUB_CONTEXT"
22+
- name: Clone self (github/codeql)
23+
uses: actions/checkout@v2
24+
- name: Set up Python 3.8
25+
uses: actions/setup-python@v2
26+
with:
27+
python-version: 3.8
28+
29+
# download artifacts from the PR job:
30+
31+
- name: Download artifact - MERGE
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
RUN_ID: ${{ github.event.workflow_run.id }}
35+
run: |
36+
gh run download --name "csv-framework-coverage-merge" --dir "out_merge" "$RUN_ID"
37+
38+
- name: Download artifact - BASE
39+
env:
40+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
RUN_ID: ${{ github.event.workflow_run.id }}
42+
run: |
43+
gh run download --name "csv-framework-coverage-base" --dir "out_base" "$RUN_ID"
44+
45+
- name: Download artifact - PR
46+
env:
47+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48+
RUN_ID: ${{ github.event.workflow_run.id }}
49+
run: |
50+
gh run download --name "pr" --dir "pr" "$RUN_ID"
51+
52+
- name: Check coverage files
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
RUN_ID: ${{ github.event.workflow_run.id }}
56+
run: |
57+
PR=$(cat "pr/NR")
58+
python misc/scripts/library-coverage/compare-files-comment-pr.py \
59+
out_merge out_base comparison.md "$GITHUB_REPOSITORY" "$PR" "$RUN_ID"
60+
- name: Upload comparison results
61+
uses: actions/upload-artifact@v2
62+
with:
63+
name: comparison
64+
path: |
65+
comparison.md
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import sys
2+
import os
3+
import settings
4+
import difflib
5+
import utils
6+
7+
"""
8+
This script compares the generated CSV coverage files with the ones in the codebase.
9+
"""
10+
11+
12+
def check_file_exists(file):
13+
if not os.path.exists(file):
14+
print("Expected file '" + file + "' doesn't exist.", file=sys.stderr)
15+
return False
16+
return True
17+
18+
19+
def ignore_line_ending(ch):
20+
return difflib.IS_CHARACTER_JUNK(ch, ws=" \r\n")
21+
22+
23+
def compare_files(file1, file2):
24+
messages = compare_files_str(file1, file2)
25+
if messages == "":
26+
return True
27+
28+
print(messages, end="", file=sys.stderr)
29+
30+
return False
31+
32+
33+
def compare_files_str(file1, file2):
34+
diff = difflib.ndiff(open(file1).readlines(),
35+
open(file2).readlines(), None, ignore_line_ending)
36+
ret = ""
37+
for line in diff:
38+
if line.startswith("+") or line.startswith("-"):
39+
ret += line
40+
41+
return ret
42+
43+
44+
def comment_pr(folder1, folder2, output_file, repo, pr_number, run_id):
45+
compare_folders(folder1, folder2, output_file)
46+
size = os.path.getsize(output_file)
47+
if size == 0:
48+
print("No difference in the coverage reports")
49+
return
50+
51+
comment = ":warning: The head of this PR and the base branch were compared for differences in the framework coverage reports. " + \
52+
"The generated reports are available in the [artifacts of this workflow run](https://github.com/" + repo + "/actions/runs/" + run_id + "). " + \
53+
"The differences will be picked up by the nightly job after the PR gets merged. "
54+
55+
if size < 2000:
56+
print("There's a small change in the CSV framework coverage reports")
57+
comment += "The following differences were found: \n\n"
58+
with open(output_file, 'r') as file:
59+
comment += file.read()
60+
else:
61+
print("There's a large change in the CSV framework coverage reports")
62+
comment += "The differences can be found in the " + \
63+
output_file + " artifact of this job."
64+
65+
post_comment(comment, repo, pr_number)
66+
67+
68+
def post_comment(comment, repo, pr_number):
69+
print("Posting comment to PR #" + str(pr_number))
70+
utils.subprocess_run(["gh", "pr", "comment", pr_number,
71+
"--repo", repo, "--body", comment])
72+
73+
74+
def compare_folders(folder1, folder2, output_file):
75+
languages = ['java']
76+
77+
return_md = ""
78+
79+
for lang in languages:
80+
expected_files = ""
81+
82+
generated_output_rst = settings.generated_output_rst.format(
83+
language=lang)
84+
generated_output_csv = settings.generated_output_csv.format(
85+
language=lang)
86+
87+
# check if files exist in both folder1 and folder 2
88+
if not check_file_exists(folder1 + "/" + generated_output_rst):
89+
expected_files += "- " + generated_output_rst + \
90+
" doesn't exist in folder " + folder1 + "\n"
91+
if not check_file_exists(folder2 + "/" + generated_output_rst):
92+
expected_files += "- " + generated_output_rst + \
93+
" doesn't exist in folder " + folder2 + "\n"
94+
if not check_file_exists(folder1 + "/" + generated_output_csv):
95+
expected_files += "- " + generated_output_csv + \
96+
" doesn't exist in folder " + folder1 + "\n"
97+
if not check_file_exists(folder2 + "/" + generated_output_csv):
98+
expected_files += "- " + generated_output_csv + \
99+
" doesn't exist in folder " + folder2 + "\n"
100+
101+
if expected_files != "":
102+
print("Expected files are missing", file=sys.stderr)
103+
return_md += "\n### " + lang + "\n\n#### Expected files are missing for " + \
104+
lang + "\n" + expected_files + "\n"
105+
continue
106+
107+
# compare contents of files
108+
cmp1 = compare_files_str(
109+
folder1 + "/" + generated_output_rst, folder2 + "/" + generated_output_rst)
110+
cmp2 = compare_files_str(
111+
folder1 + "/" + generated_output_csv, folder2 + "/" + generated_output_csv)
112+
113+
if cmp1 != "" or cmp2 != "":
114+
print("Generated file contents are not matching", file=sys.stderr)
115+
return_md += "\n### " + lang + "\n\n#### Generated file changes for " + \
116+
lang + "\n\n"
117+
if cmp1 != "":
118+
return_md += "- Changes to " + generated_output_rst + \
119+
":\n```diff\n" + cmp1 + "```\n\n"
120+
if cmp2 != "":
121+
return_md += "- Changes to " + generated_output_csv + \
122+
":\n```diff\n" + cmp2 + "```\n\n"
123+
124+
with open(output_file, 'w', newline='') as out:
125+
out.write(return_md)
126+
127+
128+
comment_pr(sys.argv[1], sys.argv[2], sys.argv[3],
129+
sys.argv[4], sys.argv[5], sys.argv[6])

misc/scripts/library-coverage/compare-files.py

Lines changed: 0 additions & 69 deletions
This file was deleted.

misc/scripts/library-coverage/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
import os
33
import csv
44
import sys
5+
import shlex
56

67

78
def subprocess_run(cmd):
89
"""Runs a command through subprocess.run, with a few tweaks. Raises an Exception if exit code != 0."""
10+
print(shlex.join(cmd))
911
return subprocess.run(cmd, capture_output=True, text=True, env=os.environ.copy(), check=True)
1012

1113

0 commit comments

Comments
 (0)