Skip to content

Commit 979034a

Browse files
committed
Add github action to generate CSV coverage report
1 parent 8cbb3ca commit 979034a

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

.github/workflows/csv-coverage.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build CSV flow coverage report
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- 'rc/**'
8+
pull_request:
9+
paths:
10+
- '.github/workflows/csv-coverage.yml'
11+
- 'misc/scripts/generate-csv-coverage-report.py'
12+
13+
jobs:
14+
build:
15+
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Clone self (github/codeql)
20+
uses: actions/checkout@v2
21+
with:
22+
path: codeql
23+
- name: Set up Python 3.8
24+
uses: actions/setup-python@v2
25+
with:
26+
python-version: 3.8
27+
- name: Download CodeQL CLI
28+
uses: dsaltares/fetch-gh-release-asset@aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c
29+
with:
30+
repo: "github/codeql-cli-binaries"
31+
version: "latest"
32+
file: "codeql-linux64.zip"
33+
token: ${{ secrets.GITHUB_TOKEN }}
34+
- name: Unzip CodeQL CLI
35+
run: unzip -d codeql-cli codeql-linux64.zip
36+
- name: Build modeled package list
37+
run: |
38+
PATH="$PATH:codeql-cli/codeql" python codeql/misc/scripts/generate-csv-coverage-report.py codeql
39+
- name: Upload modeled package list
40+
uses: actions/upload-artifact@v2
41+
with:
42+
name: csv-flow-model-coverage
43+
path: csv-flow-model-coverage-*.csv
44+
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import subprocess
2+
import json
3+
import csv
4+
import sys
5+
import os
6+
7+
"""
8+
This script runs the CSV coverage report QL query, and transforms it to a more readable format.
9+
"""
10+
11+
12+
def subprocess_run(cmd):
13+
"""Runs a command through subprocess.run, with a few tweaks. Raises an Exception if exit code != 0."""
14+
return subprocess.run(cmd, capture_output=True, text=True, env=os.environ.copy(), check=True)
15+
16+
17+
def create_empty_database(lang, extension, database):
18+
"""Creates an empty database for the given language."""
19+
subprocess_run(["codeql", "database", "init", "--language=" + lang,
20+
"--source-root=/tmp/empty", "--allow-missing-source-root", database])
21+
subprocess_run(["mkdir", "-p", database + "/src/tmp/empty"])
22+
subprocess_run(["touch", database + "/src/tmp/empty/empty." + extension])
23+
subprocess_run(["codeql", "database", "finalize",
24+
database, "--no-pre-finalize"])
25+
26+
27+
def run_codeql_query(query, database, output):
28+
"""Runs a codeql query on the given database."""
29+
subprocess_run(["codeql", "query", "run", query,
30+
"--database", database, "--output", output + ".bqrs"])
31+
subprocess_run(["codeql", "bqrs", "decode", output + ".bqrs",
32+
"--format=csv", "--no-titles", "--output", output])
33+
34+
35+
class LanguageConfig:
36+
def __init__(self, lang, ext, ql_path):
37+
self.lang = lang
38+
self.ext = ext
39+
self.ql_path = ql_path
40+
41+
42+
try: # Check for `codeql` on path
43+
subprocess_run(["codeql", "--version"])
44+
except Exception as e:
45+
print("Error: couldn't invoke CodeQL CLI 'codeql'. Is it on the path? Aborting.", file=sys.stderr)
46+
raise e
47+
48+
prefix = ""
49+
if len(sys.argv) > 1:
50+
prefix = sys.argv[1] + "/"
51+
52+
# Languages for which we want to generate coverage reports.
53+
configs = [
54+
LanguageConfig(
55+
"java", "java", prefix + "java/ql/src/meta/frameworks/Coverage.ql")
56+
]
57+
58+
for config in configs:
59+
lang = config.lang
60+
ext = config.ext
61+
query_path = config.ql_path
62+
db = "empty-" + lang
63+
ql_output = "output-" + lang + ".csv"
64+
create_empty_database(lang, ext, db)
65+
run_codeql_query(query_path, db, ql_output)
66+
67+
packages = {}
68+
parts = set()
69+
kinds = set()
70+
71+
with open(ql_output) as csvfile:
72+
reader = csv.reader(csvfile)
73+
for row in reader:
74+
package = row[0]
75+
if package not in packages:
76+
packages[package] = {
77+
"count": row[1],
78+
"part": {},
79+
"kind": {}
80+
}
81+
part = row[3]
82+
parts.add(part)
83+
if part not in packages[package]["part"]:
84+
packages[package]["part"][part] = 0
85+
packages[package]["part"][part] += int(row[4])
86+
kind = part + ":" + row[2]
87+
kinds.add(kind)
88+
if kind not in packages[package]["kind"]:
89+
packages[package]["kind"][kind] = 0
90+
packages[package]["kind"][kind] += int(row[4])
91+
92+
with open("csv-flow-model-coverage-" + lang + ".csv", 'w', newline='') as csvfile:
93+
csvwriter = csv.writer(csvfile)
94+
95+
parts = sorted(parts)
96+
kinds = sorted(kinds)
97+
98+
columns = ["package"]
99+
columns.extend(parts)
100+
columns.extend(kinds)
101+
102+
csvwriter.writerow(columns)
103+
104+
for package in sorted(packages):
105+
row = [package]
106+
for part in parts:
107+
if part in packages[package]["part"]:
108+
row.append(packages[package]["part"][part])
109+
else:
110+
row.append(None)
111+
for kind in kinds:
112+
if kind in packages[package]["kind"]:
113+
row.append(packages[package]["kind"][kind])
114+
else:
115+
row.append(None)
116+
csvwriter.writerow(row)

0 commit comments

Comments
 (0)