2016년 9월 20일 화요일

Django test에서 -s 옵션과 Encoding 문제

최근 직종을 변경하여 Django 를 공부하고 있습니다.

간단한 Test code를 돌리다가 우연히 발견한 문제가 있어 정리합니다.

다만, 끝까지 확인한 결론은 아니고 일종의 추측이 섞여 있으니 주의하세요.


문제: "manage.py test -s XXX" 와 같이 -s option을 주면 문제가 없는 Test case가 -s option을 빼면 에러가 발생.

우선 -s option이 하는 일은 이렇습니다.

-s, --nocapture       Don't capture stdout (any stdout output will be
                        printed immediately) [NOSE_NOCAPTURE]

stdout을 캡쳐하지 않는 옵션인데 이게 왜 영향을 주는 것일까요?

Test case의 문제 부분은 이렇습니다.

a = u'한글'
print(a.encode('utf8'))

그냥 유니코드를 프린트 하기 위한 문장으로 특별하지 않죠? (일반적으로 encode를 빼도 print가 알아서 잘해주지만 문제 발생을 위해 그냥 둡니다)

그런데 -s를 빼면 아래와 같은 에러가 발생합니다(일부 내용은 지웠습니다)

  File "***/lib/python2.7/site-packages/nose/plugins/capture.py", line 125, in _get_buffer
    return self._buf.getvalue()
  File "***Python.framework/Versions/2.7/lib/python2.7/StringIO.py", line 271, in getvalue
    self.buf += ''.join(self.buflist)

UnicodeDecodeError: 'ascii' codec can't decode byte 0xea in position 0: ordinal not in range(128)

StringIO가 문제군요-

알아보니 StringIO는 일종의 버퍼로 많이 쓰는데요, 유니코드 처리에도 문제가 없습니다.(cStringIO는 유니코드에 문제가 있습니다)

다만, 유니코드와 ascii가 섞일 경우가 문제가 생기는데요, 제가 생각하는 문제의 원인이 이 부분 입니다.

-s를 빼면 caputure를 위해 capture.py의 다음의 코드가 동작하는 것 같습니다.

    def start(self):
        self.stdout.append(sys.stdout)
self._buf = StringIO()
        sys.stdout = self._buf

문제는 기본 sys.stdout의 encoding이 ascii인 경우이고 제 경우 실제 기본 값이 ascii로 되어 있었습니다.

때문에 StringIO로 열어둔 스트림의 중간에 unicode와(제가 원하는 테스트코드의 인코딩) ascii(stdout의 기본 인코딩)이 섞이면서 상기와 같은 에러가 발생한다고 추측(!) 했습니다.

pdb까지 찍어서 확인하고 싶었지만, 왜인지 pdb를 붙이면 에러가 나고 또 원래 하려던 일도 아니기에 이정도 추정을 하고 본 글을 정리합니다.