-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Preface
Recently, while attempting to automate the process of updating Python standard libraries in RustPython, I became exhausted from various challenges and stopped working on it. However, I suddenly wanted to preserve these ideas, so I'm writing this post.
The problems and ideas I considered might not actually be very helpful, so it's fine if they're just treated as "Issue A" and moved on.
Since I wrote this in my native Korean before having it translated, there may be some mistranslations, but I ask for your understanding.
Problems
Regarding Python standard library updates, I see two main issues. One is that updating them requires significant labor, and the other is that it's difficult to easily determine which Python version each file is based on.
From a labor perspective
I'm not sure how others work, but in my case, I would copy files from the CPython repository to the same path in the RustPython repository, then use diff commands to try preserving existing comments.
Manually adding decorators like @unittest.expectedFailure
was quite labor-intensive.
As a solution, I considered automated scripts running on GitHub Actions. I thought about implementing it with the following structure, but couldn't proceed due to difficulties in setting up different OS hosts and personal exhaustion:
- Copy desired modules or files from the CPython repository.
- Run the following process in Windows, macOS, and Linux environments to collect reports:
- Use
cargo run -- -m test --list-cases <test>
to get all potentially executable test cases. - Run each test case in a separate process with
cargo run -- -m unittest <test>
, as some cases might cause panics or deadlocks. - Collect test success/failure/panic status and create a JSON report to upload to a separate repository (e.g., S3).
- Use
- Use LibCST to parse test files and build an inheritance graph between test classes.
- Organize failing or panicking test cases from reports and create an execution plan, considering whether to add decorators to parent classes or create arbitrary methods in child classes that call parent methods.
- Apply changes using LibCST according to the execution plan and submit a PR.
- Have humans review and finally merge it.
This approach somewhat ignores:
- Existing comments are disregarded. For example, failure reasons appended to
TODO: RUSTPYTHON
comments aren't preserved. They could be preserved, but I couldn't establish criteria for which comments to keep.
While agents like Claude Code can handle most cases well, making such automation somewhat less meaningful, I still believe automating what can be automated is good.
If this idea seems reasonable and there are edge cases I haven't considered, please add comments with additional opinions.
About standard library version management
This idea actually came up while writing the automation script.
Currently, the RustPython project takes standard library files from CPython, arbitrarily adds decorators like @unittest.expectedFailure
to pass tests, then gradually improves them.
However, since we modify the original standard library files, it's hard to immediately tell which version they're based on when comparing with CPython standard library files (though you can check commit logs), and we need to use libraries like LibCST for somewhat complex modifications.
This idea aims to use original files unchanged to make updates easier and simplify version identification (e.g., whether it's a CPython a.b version file).
Unlike the previous idea, I don't have concrete implementation details, but the concept would be:
- Create a custom test runner inheriting from
unittest
test runner and use it as the main runner. This runner would receive lists of which test cases fail or should be skipped. - The test list would be an array like
<testcase>: FAILURE | SKIP
. Based on values, it would skip or run expecting failure. Managed per OS (e.g.,testhints/linux-arm64.json
).
However, the current approach of adding decorators and comments allows searching for TODO: RUSTPYTHON
to find work targets while viewing test source code, so changing to this new idea might lose this advantage.
Also, since I've never actually attempted to implement this idea, it might be somewhat flawed.
(Korean Original)
서문
얼마전에 RustPython에 Python 표준 라이브러리들을 업데이트 하는 방식을 자동화해보려다가 여러 고민에 지쳐 더 작업을 안 하고 있지만 문득 아이디어들은 남겨놓고 싶어서 이 글을 적습니다.
제가 생각했던 문제 및 아이디어가 사실 크게 도움이 되지 않는 것일수도 있기 때문에 그냥 "이슈 A"로 취급되고 넘겨져도 좋다고 생각합니다.
제가 익숙한 한국어로 작성한 뒤 번역되어 올라가기 때문에 다소 오역될 수 있겠습니다만, 양해 부탁드립니다.
문제점
제가 생각하는 Python 표준 라이브러리 업데이트와 관련한 문제는 두가지 시각이 있습니다. 하나는 이를 업데이트하는데 노동력이 많이 필요하다는 점이고, 하나는 각각의 파일들이 현재 Python 몇 버전 기준 파일인지 쉽게 알기 어렵다는 점입니다.
노동력의 측면에서
다른 분들이 어떻게 작업하시는지 잘 모르겠지만 제 경우에는 CPython 저장소에 있는 파일을 RustPython 저장소의 같은 경로에 복사한 뒤 diff 명령어를 활용하여 기존에 있던 주석들을 그대로 보존하려고 노력하기도 했습니다.
그리고 @unittest.expectedFailure
같은 데코레이터를 손수다는 것은 꽤 노동력을 많이 들이는 작업이었습니다.
이것을 해결하는 방법으로 GitHub Actions 등에서 도는 자동화된 스크립트를 생각했습니다. 아래와 같은 구조로 작성해보리라 생각했지만 각각의 운영체제 호스트를 어떻게 구축할 지에 대한 어려움과 개인적인 체력 부족으로 인해 진행하지 못 했습니다.
- CPython 저장소에서 원하는 모듈 혹은 파일을 복사해옵니다.
- 아래의 과정을 Windows, macOS, Linux 같은 환경에서 각각 돌려 보고서를 수집합니다.
cargo run -- -m test --list-cases <test>
명령어로 실행될 여지가 있는 테스트 케이스를 모두 가져옵니다.cargo run -- -m unittest <test>
명령어로 각각의 테스트 케이스를 별도의 프로세스로 실행합니다. 이는 어떤 테스트 케이스는 패닉을 유발할 수도 있고, 데드락이 걸려 멈추지 않을수도 있기 때문입니다.- 이 테스트 성공, 실패, 패닉 여부를 모아 JSON 형식의 보고서로 만들고 별도의 저장소(e.g., S3 등)에 올립니다.
- LibCST로 테스트 파일을 파싱하여 테스트 클래스 간의 상속 관계 그래프를 구성합니다.
- 보고서로 부터 실패 혹은 패닉이 발생하는 테스트 케이스들을 모아 정리하고, 위에서 구성한 상속 관계 그래프를 참조하여 부모 클래스에 데코레이터를 달지, 자식 클래스에 부모 메소드를 호출하는 임의의 메소드를 생성하고 데코레이터를 달지 등 실행 계획을 작성합니다.
- 작성된 실행 계획에 따라 LibCST로 실제로 데코레이터를 다는 등의 변경을 가하여 PR을 올립니다.
- 사람이 이를 리뷰하고 최종적으로 머지합니다.
위 방식은 아래와 같은 것을 다소 무시합니다:
- 기존에 갖고 있던 주석은 무시됩니다. 예를 들어
TODO: RUSTPYTHON
주석에 덧붙여 있던 실패 사유는 보존되지 않습니다. 잘 보존할 수도 있지만 주석들 중 어떤 주석을 보존하여 가져가야 하는지 기준을 세우지 못 했습니다.
Claude Code 같은 에이전트가 대부분의 경우 알아서 잘 해주므로 이런 자동화의 의미가 다소 퇴색될 수 있으나 그래도 자동으로 할 수 있는 것은 자동으로 하는 것이 좋다고 생각합니다.
만약 이 아이디어가 괜찮아 보이고 제가 생각하지 못 한 엣지케이스가 있다면 코멘트로 추가로 의견을 달아주세요.
표준 라이브러리 버전 관리에 대하여
이 아이디어는 사실 위 자동화 스크립트를 작성하다가 든 생각 중 하나입니다.
현재 RustPython 프로젝트는 CPython 저장소에 있는 표준 라이브러리 파일을 가져와 @unittest.expectedFailure
같은 데코레이터를 임의로 달아 테스트를 통과할 수 있게 한 뒤 점진적으로 개선해나가는 방향을 취하고 있습니다.
하지만 일단 원본 표준 라이브러리 파일에 변경을 가하므로 CPython 표준 라이브러리 파일과 비교하는 식으로 비교할 때 어떤 버전의 파일인지 바로 알기 어렵고 (커밋 로그를 확인하면 됩니다만), LibCST 같은 라이브러리를 활용하여 다소 복잡한 수정을 가해야 한다는 점입니다.
이 아이디어는 원본 파일을 변경없이 사용하여 파일 업데이트을 쉽게 하고, CPython a.b 버전 파일인지 등의 확인을 쉽게 만드는 것이 목적입니다.
앞선 아이디어처럼 구체적인 구현 아이디어는 없지만 구상을 적어보면:
unittest
테스트 러너를 상속 받은 별도의 테스트 러너를 만들고 이를 주 테스트 러너로 사용합니다. 이 테스트 러너는 어떤 테스트 케이스들이 실패하는지, 스킵해야하는지 목록을 함께 받습니다.- 테스트 목록은
<testcase>: FAILURE | SKIP
같은 꼴의 배열입니다. 값에 따라 스킵하거나, 실행하고 실패하도록 합니다. 각 운영체제 별로 관리합니다. (e.g.,testhints/linux-arm64.json
)
다만 데코레이터 및 주석을 다는 방식의 경우 TODO: RUSTPYHON
같은 꼴로 검색하여 작업 대상을 찾음과 동시에 테스트의 소스코드도 볼 수 있게 해주므로, 이를 본 아이디어처럼 변경하게 되면 이런 장점을 잃을 우려가 있습니다.
그리고 이 아이디어는 제가 실제로 구현을 시도해본적이 없어 다소 이상한 아이디어일 수 있습니다.