Skip to content

Commit 4abaa78

Browse files
committed
Add CSV coverage PR commenter
1 parent 200126b commit 4abaa78

File tree

5 files changed

+336
-69
lines changed

5 files changed

+336
-69
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
build:
19+
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Dump GitHub context
24+
env:
25+
GITHUB_CONTEXT: ${{ toJSON(github) }}
26+
run: echo "$GITHUB_CONTEXT"
27+
- name: Clone self (github/codeql) head
28+
uses: actions/checkout@v2
29+
with:
30+
path: head
31+
- name: Clone self (github/codeql) base
32+
uses: actions/checkout@v2
33+
with:
34+
ref: ${{ github.event.pull_request.base.sha }}
35+
path: base
36+
- name: Set up Python 3.8
37+
uses: actions/setup-python@v2
38+
with:
39+
python-version: 3.8
40+
- name: Download CodeQL CLI
41+
uses: dsaltares/fetch-gh-release-asset@aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c
42+
with:
43+
repo: "github/codeql-cli-binaries"
44+
version: "latest"
45+
file: "codeql-linux64.zip"
46+
token: ${{ secrets.GITHUB_TOKEN }}
47+
- name: Unzip CodeQL CLI
48+
run: unzip -d codeql-cli codeql-linux64.zip
49+
- name: Generate CSV files on head and base of the PR
50+
run: |
51+
echo "Running generator on ${{github.sha}}"
52+
PATH="$PATH:codeql-cli/codeql" python head/misc/scripts/library-coverage/generate-report.py ci head head
53+
mkdir out_head
54+
cp framework-coverage-*.csv out_head/
55+
cp framework-coverage-*.rst out_head/
56+
57+
echo "Running generator on ${{github.event.pull_request.base.sha}}"
58+
PATH="$PATH:codeql-cli/codeql" python base/misc/scripts/library-coverage/generate-report.py ci base base
59+
mkdir out_base
60+
cp framework-coverage-*.csv out_base/
61+
cp framework-coverage-*.rst out_base/
62+
- name: Upload CSV package list
63+
uses: actions/upload-artifact@v2
64+
with:
65+
name: csv-framework-coverage-merge
66+
path: |
67+
out_head/framework-coverage-*.csv
68+
out_head/framework-coverage-*.rst
69+
- name: Upload CSV package list
70+
uses: actions/upload-artifact@v2
71+
with:
72+
name: csv-framework-coverage-base
73+
path: |
74+
out_base/framework-coverage-*.csv
75+
out_base/framework-coverage-*.rst
76+
- name: Save PR number
77+
run: |
78+
mkdir -p pr
79+
echo ${{ github.event.number }} > pr/NR
80+
- name: Upload PR number
81+
uses: actions/upload-artifact@v2
82+
with:
83+
name: pr
84+
path: pr/
85+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
build:
11+
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) }}
21+
run: echo "$GITHUB_CONTEXT"
22+
- name: Clone self (github/codeql) head
23+
uses: actions/checkout@v2
24+
with:
25+
path: head
26+
- name: Set up Python 3.8
27+
uses: actions/setup-python@v2
28+
with:
29+
python-version: 3.8
30+
31+
# download artifacts from the PR job:
32+
- name: Download artifact - HEAD
33+
uses: dawidd6/[email protected]
34+
with:
35+
workflow: csv-coverage-pr-artifacts.yml
36+
run_id: ${{ github.event.workflow_run.id }}
37+
name: csv-framework-coverage-merge
38+
path: out_head
39+
40+
- name: Download artifact - BASE
41+
uses: dawidd6/[email protected]
42+
with:
43+
workflow: csv-coverage-pr-artifacts.yml
44+
run_id: ${{ github.event.workflow_run.id }}
45+
name: csv-framework-coverage-base
46+
path: out_base
47+
48+
- name: Download artifact - PR
49+
uses: dawidd6/[email protected]
50+
with:
51+
workflow: csv-coverage-pr-artifacts.yml
52+
run_id: ${{ github.event.workflow_run.id }}
53+
name: pr
54+
path: pr
55+
56+
- name: Check coverage files
57+
run: |
58+
PR=$(cat "pr/NR")
59+
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} python head/misc/scripts/library-coverage/compare-files-comment-pr.py \
60+
out_head out_base comparison.md ${{ github.repository }} $PR ${{ github.event.workflow_run.id }}
61+
- name: Upload comparison results
62+
uses: actions/upload-artifact@v2
63+
with:
64+
name: comparison
65+
path: |
66+
comparison.md
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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])
130+
131+
132+
# def compare_generated_and_repo_files():
133+
# languages = ['java']
134+
135+
# all_ok = True
136+
137+
# for lang in languages:
138+
# repo_output_rst = settings.repo_output_rst.format(language=lang)
139+
# repo_output_csv = settings.repo_output_csv.format(language=lang)
140+
141+
# generated_output_rst = settings.generated_output_rst.format(
142+
# language=lang)
143+
# generated_output_csv = settings.generated_output_csv.format(
144+
# language=lang)
145+
146+
# exists = check_file_exists(repo_output_rst)
147+
# if not exists:
148+
# sys.exit(1)
149+
150+
# exists = check_file_exists(repo_output_csv)
151+
# if not exists:
152+
# sys.exit(1)
153+
154+
# exists = check_file_exists(generated_output_rst)
155+
# if not exists:
156+
# sys.exit(1)
157+
158+
# exists = check_file_exists(generated_output_csv)
159+
# if not exists:
160+
# sys.exit(1)
161+
162+
# docs_folder = settings.documentation_folder_no_prefix.format(
163+
# language=lang)
164+
165+
# rst_ok = compare_files(repo_output_rst, generated_output_rst)
166+
# if not rst_ok:
167+
# print("The generated file doesn't match the one in the codebase. Please check and fix file '" +
168+
# docs_folder + settings.output_rst_file_name + "'.", file=sys.stderr)
169+
# csv_ok = compare_files(repo_output_csv, generated_output_csv)
170+
# if not csv_ok:
171+
# print("The generated file doesn't match the one in the codebase. Please check and fix file '" +
172+
# docs_folder + settings.output_csv_file_name + "'.", file=sys.stderr)
173+
174+
# if not rst_ok or not csv_ok:
175+
# print("The generated CSV coverage report files for '" + lang + "' don't match the ones in the codebase. Please update the files in '" +
176+
# docs_folder + "'. The new files can be downloaded from the artifacts of this job.", file=sys.stderr)
177+
# all_ok = False
178+
# else:
179+
# print("The generated files for '" + lang +
180+
# "' match the ones in the codebase.")
181+
182+
# if not all_ok:
183+
# sys.exit(1)

0 commit comments

Comments
 (0)