코딩 가이드¶
이 문서는 PyPy 코드 베이스로 작업할 때의 코딩 요구 사항과 관례를 설명합니다. 주의 깊게 읽으시고 궁금한 점이 있으면 다시 문의해 주십시오. 이 문서는 코딩 스타일 문제에 관해서는 많이 다루지 않습니다. 다만 대체로 PEP 8을 따릅니다. 확실하지 않다면 코드베이스에 이미 있는 스타일을 따르십시오.
개요 및 동기¶
우리는 언어로서 알고리즘적 문제 뒤로 물러설 수 있는 Python의 잘 알려진 능력을 활용해, Python으로 Python 인터프리터를 작성하고 있습니다. 언뜻 보기에는 이것이 인터프리터의 작동 방식을 더 잘 이해하는 것 외에는 아무것도 이루지 못한다고 생각할 수도 있습니다. 이것만으로도 할 가치가 있겠지만, 우리에게는 훨씬 더 큰 목표가 있습니다.
CPython vs. PyPy¶
CPython 구현과 비교하면, Python이 C 코드의 역할을 맡습니다. 우리는 CPython 인터프리터를 Python 자체로 다시 작성합니다. C 수준에서 더 유연한 인터프리터를 작성하는 것을 목표로 삼을 수도 있지만, 우리는 인터프리터에 대한 대안적인 설명을 제공하기 위해 Python을 사용하고자 합니다.
명확한 장점은 이러한 설명이 더 짧고 읽기 간단하며, 많은 구현 세부 사항이 사라진다는 점입니다. 이 접근 방식의 단점은 이 인터프리터가 CPython 위에서 실행되는 한 견딜 수 없을 정도로 느리다는 것입니다.
다시 유용한 인터프리터에 도달하려면, Python에 대한 고수준 설명을 더 저수준의 설명으로 번역(translation)해야 합니다. 한 가지 비교적 간단한 방법은 PyPy 인터프리터를 전체 프로그램 분석(whole program analysis)하여 다시 C 소스를 생성하는 것입니다. 다른 방법도 여러 가지 있지만, 여기서는 이 다소 정석적인 접근 방식을 따르겠습니다.
애플리케이션 레벨과 인터프리터 레벨의 실행과 객체¶
Python은 우리 코드베이스 전체를 구현하는 데 사용되므로, 반드시 알아두어야 할 중요한 구분이 있습니다. 바로 인터프리터 레벨 객체와 애플리케이션 레벨 객체 사이의 구분입니다. 후자는 일반적인 파이썬 프로그램을 작성할 때 다루게 되는 객체입니다. 하지만 인터프리터 레벨 코드는 애플리케이션 레벨 객체의 연산을 호출하거나 속성에 접근할 수 없습니다. PyPy에서 인터프리터 레벨 코드는 바로 알아볼 수 있는데, 변수와 객체 이름의 절반이 w_로 시작하기 때문이며, 이는 그것들이 wrapped된 애플리케이션 레벨 값임을 나타냅니다.
간단한 예제로 그 차이를 보여드리겠습니다. 두 변수 a와 b의 내용을 합산하려면, 애플리케이션 레벨에서는 간단히 a+b라고 쓰겠지만 – 이에 반해 동등한 인터프리터 레벨 코드는 space.add(w_a, w_b)이며, 여기서 space는 객체 공간(object space)의 인스턴스이고, w_a와 w_b는 두 변수의 래핑된 버전을 가리키는 일반적인 이름입니다.
CPython이 동일한 문제를 어떻게 다루는지 기억해보면 도움이 됩니다: CPython에서 인터프리터 수준 코드는 C로 작성되므로, 덧셈에 대한 전형적인 코드는 PyNumber_Add(p_a, p_b)이며, 여기서 p_a와 p_b는 PyObject* 타입의 C 변수입니다. 이는 개념적으로 우리가 Python으로 인터프리터 수준 코드를 작성하는 방식과 유사합니다.
게다가, PyPy에서는 인터프리터 수준과 애플리케이션 수준의 예외를 명확히 구분해야 합니다. 애플리케이션 예외는 항상 OperationError의 인스턴스 안에 담깁니다. 이 덕분에 우리 인터프리터 수준 코드의 실패(또는 버그)를 우리가 인터프리트하고 있는 파이썬 애플리케이션 수준 프로그램에서 나타나는 실패와 쉽게 구분할 수 있습니다.
애플리케이션 레벨이 더 나은 경우가 많습니다¶
애플리케이션 레벨 코드는 훨씬 더 고수준이므로, 작성하고 디버깅하기가 그만큼 더 쉽습니다. 예를 들어, dict 객체의 update 메서드를 구현하고자 한다고 가정해 보겠습니다. 애플리케이션 레벨에서 프로그래밍하면, 예를 들어 update의 실행 가능한 정의처럼 보이는, 명백하고 단순한 구현을 작성할 수 있습니다.:
def update(self, other):
for k in other.keys():
self[k] = other[k]
만약 인터프리터 수준에서만 코드를 작성해야 한다면, 훨씬 더 저수준이고 복잡한 무언가를, 예를 들면 다음과 같은 것을 작성해야 할 것입니다:
def update(space, w_self, w_other):
w_keys = space.call_method(w_other, 'keys')
w_iter = space.iter(w_keys)
while True:
try:
w_key = space.next(w_iter)
except OperationError as e:
if not e.match(space, space.w_StopIteration):
raise # re-raise other app-level exceptions
break
w_value = space.getitem(w_other, w_key)
space.setitem(w_self, w_key, w_value)
이 인터프리터 레벨 구현은 C 소스 코드와 훨씬 더 유사해 보입니다. 그럼에도 메모리 관리 세부 사항을 포함하지 않고 파이썬의 네이티브 예외 메커니즘을 사용할 수 있기 때문에, C에 대응하는 코드보다 여전히 더 읽기 쉽습니다.
어쨌든, 애플리케이션 레벨 구현이 인터프리터 레벨 구현보다 훨씬 더 읽기 쉽고, 더 우아하며, 더 유지보수하기 쉽다는 것은 명백할 것입니다(그리고 실제로 dict.update는 PyPy에서 애플리케이션 레벨로 구현되어 있습니다).
실제로 PyPy의 거의 모든 부분에서 인터프리터 레벨 코드 중간에 애플리케이션 레벨 코드가 있는 것을 볼 수 있습니다. 몇 가지 부트스트래핑 문제(애플리케이션 레벨 함수가 실행되기 전에 객체 공간(object space)의 특정 초기화 수준이 필요함)를 제외하면, 애플리케이션 레벨 코드가 일반적으로 더 선호됩니다. 우리는 함수의 호출자가 특정 함수가 애플리케이션 레벨에서 구현되었는지 인터프리터 레벨에서 구현되었는지 알 필요가 없게 해주는 추상화(‘Gateway’라고 불림)를 가지고 있습니다.
우리의 런타임 인터프리터는 “RPython”입니다¶
C 코드 생성기를 실현 가능하게 만들기 위해 인터프리터 수준의 모든 코드는 Python 언어의 부분 집합으로 스스로를 제한해야 하며, 우리는 하위 수준 언어로의 변환을 실현 가능하게 만드는 몇 가지 규칙을 준수합니다. 애플리케이션 수준의 코드는 여전히 Python의 모든 표현력을 사용할 수 있습니다.
소스-투-소스 변환(예를 들어 Starkiller 또는 더 최근의 ShedSkin)과 달리, 저희는 Python 인터프리터를 구성하는 살아 있는 python 코드 객체로부터 변환을 시작합니다. 바이트코드를 해석하는 작업을 수행할 때, 우리의 파이썬 구현은 흔히 “RPythonic”이라고 불리는 정적인 방식으로 동작해야 합니다.
그러나 PyPy 인터프리터가 파이썬 프로그램으로 시작될 때, 특정 시점에 도달하기 전까지는 파이썬 언어 전체를 사용할 수 있지만, 그 시점부터는 실행되는 모든 것이 정적이어야 합니다. 즉, 초기화 과정에서는 우리 프로그램이 동적 코드 생성을 포함하여 파이썬의 완전한 동적 특성을 자유롭게 사용할 수 있습니다.
현재 구현에서 상당히 우아한 예시를 찾을 수 있습니다: Python 인터프리터의 모든 옵코드 정의를 위해, dis 모듈을 임포트하여 바이트코드 인터프리터를 초기화하는 데 사용합니다. (pypy/interpreter/pyopcode.py의 __initclass__를 참고하십시오.) 이는 PyPy에 추가 모듈을 더하지 않아도 되게 해줍니다. 임포트 코드는 시작 시점에 실행되며, 우리는 CPython 내장 임포트 함수를 사용할 수 있습니다.
시작 코드가 완료되면, 그 결과로 생성되는 모든 객체, 함수, 코드 블록 등은 아래에서 설명할 특정 런타임 제약 조건을 준수해야 합니다. 이것이 이러한 이유에 대한 배경입니다: 변환 과정에서, RPython에 정의된 제약을 활용하는 전체 프로그램 분석(“타입 추론”)이 수행됩니다. 예를 들어, 이를 통해 코드 생성기는 순수 정수 객체에 대해 효율적인 기계 수준의 대체 코드를 생성할 수 있습니다.
래핑 규칙¶
래핑(Wrapping)¶
PyPy는 두 가지 수준의 Python 소스 코드로 이루어져 있습니다: 한편으로는 일반 Python 코드처럼 보이고 Python 코드에서 기대할 수 있는 방식으로 일부 기능을 구현하는 애플리케이션 수준 코드가 있습니다(예를 들어 zip()과 같은 일부 내장 함수의 순수 Python 구현을 제공할 수 있습니다). 인터프리터 데이터와 객체를 더 직접적으로 조작해야 하는 기능을 위한 인터프리터 수준 코드도 있습니다(예를 들어 인터프리터의 메인 루프, 다양한 객체 공간(object space) 등).
애플리케이션 레벨 코드는 객체 공간(object space)을 명시적으로 보지 않습니다: 조작하는 객체를 지원하기 위해 객체 공간을 사용해 실행되지만, 이는 암묵적입니다. 애플리케이션 레벨 코드에는 특별한 규칙이 필요하지 않습니다. 이어지는 내용은 인터프리터 레벨 코드에 관한 것만 다룹니다. (이상적으로는, 혼동을 피하기 위해 애플리케이션 레벨 변수를 space나 w_xxx라고 부르지 않아야 합니다.)
위 예제에서 아낌없이 사용된 w_ 접두사는 PyPy 코딩 관례에 따라, 우리가 wrapped*(또는 *boxed) 객체, 즉 객체 공간(object space)이 대응하는 응용 프로그램 수준 객체를 구현하기 위해 구성하는 인터프리터 수준 객체를 다루고 있음을 나타냅니다. 각 객체 공간(object space)은 단순한 내장 타입의 객체에 대해 두 수준 사이를 오가는 wrap, unwrap, int_w, interpclass_w 등의 연산을 제공합니다. 각 객체 공간은 또한 어느 정도의 내부 구조를 가진 적절한 인터프리터 수준 클래스로 다른 Python 타입들을 구현합니다.
예를 들어, 애플리케이션 레벨의 Python list는 표준 객체 공간(standard object space)에 의해 W_ListObject의 인스턴스로 구현되며, 이 인스턴스는 인스턴스 속성 wrappeditems (애플리케이션 레벨 리스트의 항목들을 래핑된 객체로 담고 있는 인터프리터 레벨 리스트)를 가지고 있습니다.
규칙은 아래에서 더 자세히 설명합니다.
명명 규칙¶
space: 객체 공간(object space)은 인터프리터 레벨 코드에서만 볼 수 있으며, 관례적으로space라는 이름으로 전달됩니다.w_xxx: 애플리케이션 수준 코드에서 보이는 모든 객체는 객체 공간(object space)이 명시적으로 관리하는 객체입니다. 인터프리터 수준의 관점에서 보면, 이것은 래핑된 객체라고 불립니다.w_접두사는 모든 유형의 애플리케이션 수준 객체에 사용됩니다.xxx_w: 래핑된 객체를 위한 인터프리터 수준의 컨테이너로, 예를 들어 래핑된 객체를 포함하는 리스트나 딕셔너리입니다. 리스트나 딕셔너리인 래핑된 객체와 혼동해서는 안 됩니다: 이것들은 일반적인 래핑된 객체이므로w_접두사를 사용합니다.
w_xxx에 대한 연산¶
핵심 바이트코드 인터프리터는 래핑된 객체를 블랙박스로 취급합니다. 이를 직접 조사하는 것은 허용되지 않습니다. 허용되는 연산은 모두 객체 공간(object space)에 구현되어 있으며, space.xxx()라고 불리고, 여기서 xxx는 표준 연산 이름(add, getattr, call, eq 등)입니다. 이는 object space document에 문서화되어 있습니다.
간단한 경고: w_x == w_y나 w_x is w_y를 하지 마세요! 이 규칙의 근거는, 두 래퍼가 애플리케이션 레벨에서 같은 객체처럼 보이는 것을 담고 있더라도 어떤 식으로든 관련되어 있을 이유가 없다는 것입니다. 동등성을 확인하려면 space.is_true(space.eq(w_x, w_y))를 사용하거나, 인터프리터 레벨의 bool을 직접 반환하는 단축형 space.eq_w(w_x, w_y)를 사용하는 것이 더 좋습니다. 동일성을 확인하려면 space.is_true(space.is_(w_x, w_y))를 사용하거나, space.is_w(w_x, w_y)를 사용하는 것이 더 좋습니다.
애플리케이션 레벨 예외¶
인터프리터 레벨 코드는 예외를 자유롭게 사용할 수 있습니다. 하지만 애플리케이션 레벨의 모든 예외는 인터프리터 레벨에서 OperationError로 표현됩니다. 다시 말해, 애플리케이션 레벨에서 보일 가능성이 있는 모든 예외는 내부적으로 OperationError입니다. 이는 객체 공간(object space) 연산(space.add() 등)에서 보고되는 모든 오류에 해당합니다.
애플리케이션 수준 예외를 발생시키려면:
from pypy.interpreter.error import oefmt
raise oefmt(space.w_XxxError, "message")
raise oefmt(space.w_XxxError, "file '%s' not found in '%s'", filename, dir)
raise oefmt(space.w_XxxError, "file descriptor '%d' not open", fd)
특정 애플리케이션 레벨 예외를 잡으려면:
try:
...
except OperationError as e:
if not e.match(space, space.w_XxxError):
raise
...
이 구문은 애플리케이션 수준의 모든 예외를 잡아내므로, 관심 있는 특정 w_XxxError에 맞춰 대조하고 다른 예외는 다시 발생시켜야 합니다. 예외 인스턴스 e는 검사할 수 있는 두 개의 속성인 e.w_type과 e.w_value를 가지고 있습니다. 예외를 대조하는 데 e.w_type을 사용하지 마십시오. 이는 하위 클래스의 인스턴스인 예외를 놓치게 됩니다.
PyPy의 모듈¶
애플리케이션 프로그램에서 보이는 모듈은 인터프리터 또는 애플리케이션 레벨 파일에서 임포트됩니다. PyPy는 CPython 표준 라이브러리의 거의 모든 파이썬 모듈을 재사용하며, 현재 버전 2.7.8을 기준으로 합니다. 우리는 때때로 modify modules를, 그리고 - 더 자주 - 회귀 테스트를 수정해야 하는데, 이는 그것들이 CPython의 구현 세부사항에 의존하기 때문입니다.
원본 CPython 모듈을 단순히 수정하는 것이 아니라 처음부터 다시 작성해야 하는 경우, 이를 순수 애플리케이션 레벨 모듈로서 lib_pypy/에 넣습니다.
인터프리터 수준 객체에 접근해야 할 때는 모듈을 pypy/module에 넣습니다. 이러한 모듈은 mixed module mechanism을 사용하여 구현에 인터프리터 수준과 애플리케이션 수준 부분을 모두 편리하게 사용할 수 있게 합니다. 순수 인터프리터 수준 모듈을 위한 별도의 기능은 없으며, 그냥 혼합 모듈(mixed module)을 작성하고 애플리케이션 수준 부분을 비워두면 됩니다.
모듈 구현의 위치 결정¶
py.py를 실행하는 동안 모듈이 어디에서 오는지 대화형으로 알아볼 수 있습니다. 다음은 가능한 위치들의 예시입니다.:
>>>> import sys
>>>> sys.__file__
'/home/hpk/pypy-dist/pypy/module/sys'
>>>> import cPickle
>>>> cPickle.__file__
'/home/hpk/pypy-dist/lib_pypy/cPickle..py'
>>>> import os
>>>> os.__file__
'/home/hpk/pypy-dist/lib-python/2.7/os.py'
>>>>
모듈 디렉터리 / 임포트 순서¶
PyPy가 파이썬 모듈을 검색하는 순서는 다음과 같습니다:
pypy/module
sys과__builtin__모듈처럼 인터프리터/앱 레벨이 혼합된 내장 모듈.
PYTHONPATH의 내용
PYTHONPATH환경 변수에 지정된,:로 구분된 디렉터리 목록의 각 디렉터리에서 애플리케이션 레벨 모듈을 조회합니다.
lib_pypy/
모듈의 순수 Python 재구현을 포함합니다.
lib-python/2.7/
수정된 CPython 라이브러리입니다.
CPython 라이브러리 모듈 또는 회귀 테스트 수정하기¶
PyPy는 CPython과 매우 호환되지만, 표준 라이브러리 사본에 포함된 모듈을 변경해야 할 때가 종종 있습니다. 이는 흔히 PyPy가 기본적으로 모든 새 스타일 클래스로 동작하는 반면, CPython은 일부 클래스가 예전 스타일임에 의존하는 부분이 여러 곳 있기 때문입니다.
저희는 이러한 변경 사항을 그대로 유지 관리합니다. 무엇이 변경되었는지 확인할 수 있도록, 수정되지 않은 CPython 표준 라이브러리(stdlib)를 담고 있는 vendor/stdlib이라는 브랜치를 두고 있습니다.
인터프리터/애플리케이션 레벨이 혼합된 모듈 구현하기¶
모듈이 PyPy의 인터프리터 레벨에 접근해야 하는 경우에는 혼합 모듈(mixed module)로 구현됩니다.
“Mixed modules”는 pypy/module 안에 있는 디렉터리로, 모듈 안의 각 이름이 어디서 오는지에 대한 명세가 담긴 __init__.py 파일을 가지고 있습니다. 지정된 이름만 Mixed Module의 애플레벨(applevel) 네임스페이스로 내보내집니다.
때로는 C(또는 대상 언어)로 실제로 몇몇 함수를 작성해야 할 필요가 있습니다. 자세한 내용은 rffi를 참고하십시오.
애플리케이션 레벨 정의¶
애플리케이션 레벨 명세는 pypy/module의 디렉터리에 있는 __init__.py 파일에 있는 appleveldefs 사전에서 찾을 수 있습니다. 예를 들어, pypy/module/__builtin__/__init__.py에서 __builtin__.locals이 어디서 오는지 명시하는 다음과 같은 항목을 찾을 수 있습니다.:
...
'locals' : 'app_inspect.locals',
...
app_ 접두사는 서브모듈 app_inspect가 애플리케이션 레벨에서 해석됨을 나타내며, 이에 따라 locals에 대한 래핑된 함수 값이 추출됩니다.
인터프리터 레벨 정의¶
인터프리터 레벨 명세는 pypy/module 디렉터리들의 __init__.py 파일에 있는 interpleveldefs 딕셔너리에서 찾을 수 있습니다. 예를 들어, pypy/module/__builtin__/__init__.py에서 다음 항목은 __builtin__.len이 어디서 오는지 명시합니다.:
...
'len' : 'operation.len',
...
operation서브모듈은 인터프리터 레벨에 위치하며, len은 애플리케이션 레벨에 노출될 것으로 예상됩니다. 다음은 operation.len()의 정의입니다.:
def len(space, w_obj):
"len(object) -> integer\n\nReturn the number of items of a sequence or mapping."
return space.len(w_obj)
노출된 인터프리터 레벨 함수는 보통 space인자와 감싸인 값 몇 개를 받습니다 (래핑 규칙참고).
또한 interpleveldefs 딕셔너리에서 편리한 단축 표현을 사용할 수 있습니다: 즉, 괄호 안의 표현식으로 인터프리터 레벨 표현식을 (파일에서 간접적으로 가져오는 대신) 직접 지정하는 것입니다.:
...
'None' : '(space.w_None)',
'False' : '(space.w_False)',
...
인터프리터 수준 표현식은 실행될 때 space바인딩을 가집니다.
pypy/module 아래에 항목(예: mymodule)을 추가하면 py.py와 translate.py용으로 –withmod-mymodule 및 –withoutmod-mymodule(후자가 기본값)과 같은 새 설정 옵션이 자동으로 생성됩니다.
lib_pypy/의 모듈 테스트¶
“py.test” 또는 “python ../../pypy/test_all.py” 같은 테스트 도구를 호출해 lib_pypy 계층 구조에 대한 테스트를 실행하려면, pypy/module/test_lib_pypy/ 디렉터리로 이동하면 됩니다. 이를 통해 파이썬으로 작성한 재구현을 CPython과 비교하여 빠르게 테스트할 수 있습니다.
pypy/module에서 모듈 테스트하기¶
pypy/module이나 하위 디렉터리로 이동한 후 평소대로 테스트를 실행하면 됩니다.
lib-python의 모듈 테스트하기¶
CPython의 회귀 테스트가 PyPy에 대해 실행되도록 하려면 lib-python/디렉터리로 이동하여 테스트 도구를 실행함으로써 호환성 테스트를 시작할 수 있습니다. (XXX 테스트 보고서 생성을 위한 windows 호환성 확인 필요.)
명명 규칙 및 디렉터리 구조¶
디렉터리 및 파일 명명 규칙¶
- 디렉터리/모듈/네임스페이스는 항상 lowercase입니다
- 디렉터리와 파일 이름에는 절대 복수형을 사용하지 마세요
__init__.py는pypy/objspace/*와pypy/module/*/__init__.py를 제외하면 보통 비어 있습니다.- 디렉토리 중첩 수준을 4단계 이상 사용하지 마십시오.
- 파일 이름은 간결하고 자동 완성하기 쉽게 유지합니다.
파이썬 객체의 명명(命名)¶
- 클래스 이름은 CamelCase입니다
- 함수/메서드는 소문자이며
_로 구분됩니다 - 객체 공간(object space) 클래스는
XyzObjSpace로 표기합니다. 예를 들어- StdObjSpace
- FlowObjSpace
- 인터프리터 수준과 ObjSpace에서는 모든 박스된(boxed) 값이 “래핑된 값(wrapped values)”을 나타내기 위해
w_로 시작합니다. 여기에는 w_self도 포함됩니다. 애플리케이션 레벨 파이썬 전용 코드에서는w_를 사용하지 마십시오.
저장소에 커밋 및 브랜칭하기¶
여러 사람이 diff를 읽고 있으므로 좋은 로그 메시지를 작성해야 합니다.
이전에
trunk라고 불리던 것은 mercurial에서default브랜치라고 불립니다. mercurial에서 브랜치는 항상 저장소의 나머지 부분과 함께 푸시됩니다.try1브랜치를 생성하려면(try1이라는 이름의 브랜치가 아직 존재하지 않는다고 가정할 때) 다음을 수행해야 합니다:hg branch try1
브랜치는 커밋을 해야만 저장소에 기록됩니다. 기본 브랜치로 다시 전환하려면:
hg update default
자세한 내용은 도움말을 사용하거나 official wiki를 참고하세요:
hg help branch
개발 버그/기능 트래커 사용하기¶
저희는 issues 추적과 pull-requests를 위해 https://github.com/pypy/pypy 를 사용합니다.
PyPy에서의 테스트¶
저희 테스트는 보일러플레이트 없이 유닛 테스트를 작성할 수 있게 해주는 py.test도구를 기반으로 합니다. 디렉터리 내 모듈들의 모든 테스트는 보통 test라는 하위 디렉터리에 있습니다. 유닛 테스트에는 기본적으로 두 가지 종류가 있습니다:
- 인터프리터 레벨 테스트입니다. PyPy의 인터프리터와 같은 레벨에서 실행됩니다.
- 애플리케이션 레벨 테스트. 이들은 애플리케이션 레벨에서 실행되며, 이는 순수한 파이썬 코드처럼 보이지만 실제로는 PyPy가 인터프리터로 실행한다는 의미입니다.
인터프리터 레벨 테스트¶
다음과 같이 테스트 함수와 메서드를 작성할 수 있습니다:
def test_something(space):
# use space ...
class TestSomething(object):
def test_some(self):
# use 'self.space' here
테스트 함수에는 접두사 test가, 테스트 클래스에는 Test가 필수라는 점에 유의하십시오. 두 경우 모두 py.test도구 덕분에 모듈 전역 수준에서 Python 모듈을 임포트하고 일반 ‘assert’ 문을 사용할 수 있습니다.
애플리케이션 레벨 테스트¶
PyPy의 준수성(conformance)과 정상 동작 여부를 테스트하는 데는 특정한 코딩 스타일이나 제약을 의식할 필요가 없는 “일반적인” 애플리케이션 레벨 Python 코드를 작성하는 것으로 충분한 경우가 많습니다. 선택의 여지가 있다면 저희는 종종 apptest_ 접두사로 시작하는 파일명에 있는 애플리케이션 레벨 테스트를 사용하는데, 이는 다음과 같이 생겼습니다.:
# spaceconfig = {"usemodules":["array"]}
def test_this():
# application level test code
이러한 애플리케이션 레벨 테스트 함수는 PyPy 위에서 실행되며, 즉 인터프리터 세부 사항에 접근할 수 없습니다.
기본적으로 이들은 호스트 인터프리터 위에서 실행되는 번역(translation)되지 않은 PyPy 위에서 실행됩니다. -D 옵션을 전달하면, 이들은 호스트 인터프리터 위에서 직접 실행되며, 이 경우 호스트 인터프리터는 보통 번역(translation)된 pypy 실행 파일입니다.:
pypy3 -m pytest -D pypy/
인터프리트 모드에서는 pytest 기능의 일부만 사용할 수 있다는 점에 유의하십시오. 객체 공간(object space)을 설정하려면, 호스트 인터프리터가 선택적 spaceconfig 선언을 파싱합니다. 이 선언은 유효한 json dict 형식이어야 합니다.
혼합 레벨 테스트(사용 중단됨)¶
Mixed-level 테스트는 application-level 테스트와 비슷합니다. 차이점은 그저 interp-level 테스트 파일 안에 삽입된 app-level 코드 조각이라는 점입니다. 예를 들면 다음과 같습니다:
class AppTestSomething(object):
def test_this(self):
# application level test code
전역 수준에서 임포트한 모듈은 사용할 수 없습니다. 이러한 모듈은 인터프리터 수준에서 임포트되지만, 테스트 코드는 애플리케이션 수준에서 실행되기 때문입니다. 모듈을 사용해야 하는 경우, 테스트 함수 내에서 임포트해야 합니다.
데이터는 AppTest의 setup_class 메서드를 사용하여 AppTest로 전달할 수 있습니다. 거기서 클래스에 첨부되고 w_로 시작하는 모든 래핑된 객체는 실제 테스트 메서드에서 self를 통해(단 w_는 제외) 접근할 수 있습니다. 예시:
class AppTestErrno(object):
def setup_class(cls):
cls.w_d = cls.space.wrap({"a": 1, "b", 2})
def test_dict(self):
assert self.d["a"] == 1
assert self.d["b"] == 2
또 다른 방법은 cls.space.appexec를 사용하는 것입니다. 예를 들어:
class AppTestSomething(object):
def setup_class(cls):
arg = 2
cls.w_result = cls.space.appexec([cls.space.wrap(arg)], """(arg):
return arg ** 6
""")
def test_power(self):
assert self.result == 2 ** 6
이는 주어진 인자로 앱 레벨에서 코드 문자열 함수를 실행합니다. setup_class에서는 w_result를 사용하지만, 테스트에서는 self.result를 사용한다는 점에 유의하십시오. 다음은 이후 테스트에서 사용할 수 있는 앱 레벨 클래스를 setup_class에서 정의하는 방법입니다.:
class AppTestSet(object):
def setup_class(cls):
w_fakeint = cls.space.appexec([], """():
class FakeInt(object):
def __init__(self, value):
self.value = value
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
if other == self.value:
return True
return False
return FakeInt
""")
cls.w_FakeInt = w_fakeint
def test_fakeint(self):
f1 = self.FakeInt(4)
assert f1 == 4
assert hash(f1) == hash(4)
명령줄 도구 test_all¶
PyPy의 거의 모든 테스트는 다음을 실행하여 수행할 수 있습니다:
python test_all.py file_or_directory
이는 py/bin/디렉터리에 있는 일반적인 py.test유틸리티의 동의어입니다. 테스트 실행을 수정하는 스위치를 사용하려면 -h옵션을 전달하십시오.
커버리지 보고서¶
커버리지 보고서를 얻기 위해 pytest-cov플러그인이 포함되어 있습니다. 이는 몇 가지 추가 요구 사항( coverage 와 cov-core )을 추가하며, 이것들이 설치되면 커버리지 테스트는 다음을 통해 실행할 수 있습니다:
python test_all.py --cov file_or_direcory_to_cover file_or_directory
문서 및 웹사이트 변경하기¶
로컬 체크아웃에 있는 documentation/website 파일¶
PyPy 문서의 대부분은 pypy/doc에 보관되어 있습니다. ReST 마크업된 파일을 담고 있는 ‘.rst’ 파일을 단순히 편집하거나 추가할 수 있습니다. ReST quickstart가 있지만, 기존 문서를 살펴보고 어떻게 동작하는지 확인해 볼 수도 있습니다.
https://pypy.org/ 웹사이트는 별도로 관리된다는 점에 유의하십시오. 이 웹사이트는 https://github.com/pypy/pypy.org 저장소에 있습니다.
문서/웹사이트 변경 사항을 자동으로 테스트합니다¶
참조 무결성과 ReST 준수 여부를 자동으로 확인합니다. 테스트를 실행하려면 sphinx가 설치되어 있어야 합니다. 그런 다음 문서 디렉터리의 로컬 체크아웃으로 이동하여 Makefile을 실행합니다.:
cd pypy/doc
make html
실패가 보이지 않는다면, 여러분의 수정 사항이 적어도 ReST 오류나 잘못된 로컬 참조를 만들어내지 않을 가능성이 높습니다. 이제 문서 디렉터리에 브라우저로 가리켜 볼 수 있는 .html 파일들이 생성되어 있을 것입니다!
추가로, 문서 이슈 내부의 원격 참조(remote references)도 확인하고 싶다면:
make linkcheck
원격 URL이 접근 가능한지 확인합니다.