Skip to content

Commit 633ce6a

Browse files
authored
ci: Add GH Action to detect compatibility breaking chages (#2877)
1 parent 787dd9e commit 633ce6a

File tree

2 files changed

+95
-13
lines changed

2 files changed

+95
-13
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Check interface compatibility
2+
3+
on:
4+
pull_request_target:
5+
6+
permissions:
7+
pull-requests: write
8+
9+
jobs:
10+
check-interface-compatibility:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout the base branch
15+
uses: actions/checkout@v3
16+
17+
- uses: actions/setup-python@v4
18+
with:
19+
python-version: "3.10"
20+
21+
- name: Extract original public interfaces
22+
run: |
23+
make init
24+
bin/public_interface.py extract > ${{ runner.temp }}/interfaces.original.json
25+
26+
- name: Backup original bin/public_interface.py
27+
run: |
28+
# Keep a copy of bin/public_interface.py to use on the base branch
29+
# So we are using the same bin/public_interface.py to process old/new codebase.
30+
# Because the step has credential of GitHub token to post comments,
31+
# always use the original version that is in our repository.
32+
cp bin/public_interface.py ${{ runner.temp }}/public_interface.py
33+
34+
- name: Checkout the PR
35+
uses: actions/checkout@v3
36+
with:
37+
ref: "refs/pull/${{ github.event.number }}/merge"
38+
39+
- name: Extract PR public interfaces
40+
run: |
41+
make init
42+
# Recover bin/public_interface.py
43+
cp ${{ runner.temp }}/public_interface.py bin/public_interface.py
44+
bin/public_interface.py extract > ${{ runner.temp }}/interfaces.new.json
45+
46+
- name: Detect compatibility breaking changes
47+
id: detect
48+
run: |
49+
exit_code=0
50+
output=$(bin/public_interface.py check ${{ runner.temp }}/interfaces.original.json ${{ runner.temp }}/interfaces.new.json) || exit_code=$?
51+
52+
# Show the output in job summary
53+
echo "$output" >> $GITHUB_STEP_SUMMARY
54+
55+
# Update steps.detect.outputs.change_detected=true for next steps
56+
if [ "$exit_code" -ne 0 ]; then
57+
echo "change_detected=true" >> $GITHUB_OUTPUT
58+
fi
59+
60+
# Save the output for next step to process it,
61+
# and saving it into a file can avoid the need of escaping in github-script.
62+
echo "$output" > ${{ runner.temp }}/output.md
63+
64+
- name: Post GitHub PR comment
65+
if: ${{ steps.detect.outputs.change_detected }}
66+
uses: actions/github-script@v6
67+
with:
68+
github-token: ${{secrets.GITHUB_TOKEN}}
69+
script: |
70+
const fs = require('fs');
71+
const output = fs.readFileSync('${{ runner.temp }}/output.md', 'utf8')
72+
github.rest.issues.createComment({
73+
issue_number: context.issue.number,
74+
owner: context.repo.owner,
75+
repo: context.repo.repo,
76+
body: output
77+
})
78+
79+
- name: Show detection output
80+
if: ${{ steps.detect.outputs.change_detected }}
81+
run: |
82+
cat ${{ runner.temp }}/output.md
83+
exit 1

bin/public_interface.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,16 @@ def _scan_methods_in_class(self, class_name: str, _class: Any) -> None:
9090
def _print(signature: Dict[str, inspect.Signature], variables: Set[str]) -> None:
9191
result: Dict[str, Any] = {"routines": {}, "variables": sorted(variables)}
9292
for key, value in signature.items():
93-
for parameter in value.parameters.values():
94-
result["routines"][key] = [
95-
{
96-
"name": parameter.name,
97-
"kind": parameter.kind.name,
98-
"default": parameter.default,
99-
}
100-
if parameter.default != inspect.Parameter.empty
101-
else {"name": parameter.name, "kind": parameter.kind.name}
102-
for parameter in value.parameters.values()
103-
]
93+
result["routines"][key] = [
94+
{
95+
"name": parameter.name,
96+
"kind": parameter.kind.name,
97+
"default": parameter.default,
98+
}
99+
if parameter.default != inspect.Parameter.empty
100+
else {"name": parameter.name, "kind": parameter.kind.name}
101+
for parameter in value.parameters.values()
102+
]
104103
print(json.dumps(result, indent=2, sort_keys=True))
105104

106105

@@ -132,12 +131,12 @@ def print_markdown(
132131
print("\n## Deleted module level variables")
133132
for name in self.deleted_variables:
134133
print(f"- {name}")
135-
if self.deleted_variables:
134+
if self.deleted_routines:
136135
print("\n## Deleted routines")
137136
for name in self.deleted_routines:
138137
print(f"- {name}")
139138
if self.incompatible_routines:
140-
print("\n## Deleted routines")
139+
print("\n## Incompatible routines")
141140
for name in self.incompatible_routines:
142141
before = f"({', '.join(self._argument_to_str(arg) for arg in original_routines[name])})"
143142
after = f"({', '.join(self._argument_to_str(arg) for arg in routines[name])})"

0 commit comments

Comments
 (0)