Skip to content

Commit

Permalink
Add typechecking CI (#52)
Browse files Browse the repository at this point in the history
* Add typechecking CI

* make it work with new files
  • Loading branch information
rhelmot authored Jan 2, 2025
1 parent 7abc3bb commit 7519160
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/angr-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ jobs:
- run: /root/scripts/ga-lint.sh
name: Run linter

typecheck:
name: Typecheck
runs-on: ubuntu-22.04
container:
image: ${{ inputs.container_image }}
needs: build
steps:
- uses: actions/download-artifact@v4
with:
name: build_archive
- run: /root/scripts/ga-typecheck.sh
name: Run type checker

test:
name: Test
runs-on: ubuntu-22.04
Expand Down
20 changes: 20 additions & 0 deletions ci-image/scripts/ga-typecheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash -ex

BASEDIR=$(dirname $(dirname $0))
SCRIPTS=$BASEDIR/scripts

tar -I zstd -xf build.tar.zst
cd build

source virtualenv/bin/activate
pip install pyright

cd src/${GITHUB_REPOSITORY##*/}
HEAD_REV="$(git rev-parse --abbrev-ref HEAD)"
if [[ $GITHUB_REF == "master" ]]; then
BASE_REV="$(git rev-parse --abbrev-ref HEAD~)"
else
BASE_REV="$(git rev-parse --abbrev-ref master)"
fi

python $SCRIPTS/typecheck.py $BASE_REV $HEAD_REV
81 changes: 81 additions & 0 deletions ci-image/scripts/typecheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import sys
import subprocess
import json
import dataclasses
import os

@dataclasses.dataclass
class FileReport:
filename: str
diagnostics: list[tuple[int, int, str, str]]
errors: int = 0
warnings: int = 0
lines: int = 0
score: float = 0.

def count_lines(filename):
with open(filename, 'rb') as fp:
return sum(1 for _ in fp)

def typecheck_files(filenames):
filenames = [filename for filename in filenames if os.path.exists(filename)]
if not filenames:
return {}
proc = subprocess.run(["pyright", "--outputjson", *filenames], text=False, check=False, stdout=subprocess.PIPE)
pyright_report = json.loads(proc.stdout)
my_report = {os.path.realpath(filename): FileReport(filename, diagnostics=[], lines=count_lines(filename)) for filename in filenames}
for diagnostic in pyright_report["generalDiagnostics"]:
severity = diagnostic["severity"]
filename = diagnostic["file"]
if severity == "error":
my_report[filename].errors += 1
elif severity == "warning":
my_report[filename].warnings += 1
start = diagnostic["range"]["start"]
my_report[filename].diagnostics.append((start["line"], start["character"], severity, diagnostic["message"]))

for report in my_report.values():
report.score = (report.errors * 10 + report.warnings) / report.lines

return my_report

def typecheck_diff(rev1, rev2):
print(f"Comparing {rev1} --> {rev2}")
print()
filenames = subprocess.check_output(["git", "diff", "--name-only", f"{rev1}...{rev2}"], text=True).splitlines()
subprocess.check_call(["git", "checkout", rev1], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
report1 = typecheck_files(filenames)
subprocess.check_call(["git", "checkout", rev2], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
report2 = typecheck_files(filenames)

status = 0

for filename in filenames:
filename = os.path.realpath(filename)
if filename not in report2:
continue
if filename in report1:
base_score = report1[filename].score
else:
base_score = 0
e2 = report2[filename]
if e2.score > base_score:
status += 1
print(f"### {filename} badness increased from {base_score} to {e2.score}. Please fix:")
for line, char, severity, text in sorted(e2.diagnostics):
print(f"{filename}:{line}:{char}: [{severity}] {text}")
elif e2.score < base_score:
print(f"### {filename} badness decreased from {base_score} to {e2.score}. Nice!")
else:
print(f"### {filename} badness remained at {base_score}. Nice!")

if status > 0:
print(f"\n{status} files regressed. Fix them!")
return 1
else:
print("\nYou did it!")
return 0

if __name__ == '__main__':
sys.exit(typecheck_diff(sys.argv[1], sys.argv[2]))

0 comments on commit 7519160

Please sign in to comment.