Skip to content

Commit

Permalink
Compile module source from bytes to avoid encoding issues
Browse files Browse the repository at this point in the history
  • Loading branch information
lordmauve committed May 6, 2019
1 parent 31068aa commit 97c989d
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 132 deletions.
5 changes: 5 additions & 0 deletions pgzero/clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def __init__(self):
self.events = []
self._each_tick = []

def clear(self):
"""Remove all handlers from this clock."""
self.events.clear()
self._each_tick.clear()

def schedule(self, callback, delay):
"""Schedule callback to be called once, at `delay` seconds from now.
Expand Down
40 changes: 29 additions & 11 deletions pgzero/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from .game import PGZeroGame, DISPLAY_FLAGS
from . import loaders
from . import builtins
from . import clock


# The base URL for Pygame Zero documentation
Expand Down Expand Up @@ -98,8 +98,12 @@ def main():
if __debug__:
warnings.simplefilter('default', DeprecationWarning)
path = args.script
load_and_run(path, repl=args.repl)

with open(path) as f:

def load_and_run(path, repl=False):
"""Load and run the given Python file as the main PGZero game module."""
with open(path, 'rb') as f:
src = f.read()

code = compile(src, os.path.basename(path), 'exec', dont_inherit=True)
Expand All @@ -114,25 +118,39 @@ def main():
# This disables the 'import pgzrun' module
sys._pgzrun = True

prepare_mod(mod)
loaders.set_root(path)
prepare()
exec(code, mod.__dict__)
run_mod(mod, repl=args.repl)

pygame.display.init()
try:
run_mod(mod, repl=repl)
finally:
# Clean some of the state we created, useful in testing
pygame.display.quit()
clock.clock.clear()
del sys.modules[name]


def prepare_mod(mod):
"""Prepare a module to run as a Pygame Zero program.
def prepare():
"""Prepare to execute the module code for Pygame Zero.
mod is a loaded module object.
When executing the module some things need to already exist:
This sets up things like screen, loaders and builtins, which need to be
set before the module globals are run.
* Our extra builtins need to be defined (by copying them into Python's
`builtins` module)
* A screen needs to be created (because we use convert_alpha() to convert
Sprite surfaces for blitting to the screen).
"""
loaders.set_root(mod.__file__)
# An icon needs to exist before the window is created.
PGZeroGame.show_default_icon()
pygame.display.set_mode((100, 100), DISPLAY_FLAGS)

# Copy pgzero builtins into system builtins
from . import builtins as pgzero_builtins
import builtins as python_builtins
for k, v in builtins.__dict__.items():
for k, v in vars(pgzero_builtins).items():
python_builtins.__dict__.setdefault(k, v)


Expand Down
7 changes: 7 additions & 0 deletions test/game_tests/utf8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
WIDTH = HEIGHT = 300


def draw():
'''😂'''
screen.fill('blue')
screen.draw.text('foo', color='white', center=(150, 150))
232 changes: 117 additions & 115 deletions test/test_actor.py
Original file line number Diff line number Diff line change
@@ -1,115 +1,117 @@
import unittest

import pygame

from pgzero.actor import calculate_anchor, Actor
from pgzero.loaders import set_root


TEST_MODULE = "pgzero.actor"
TEST_DISP_W, TEST_DISP_H = (200, 100)


pygame.init()
pygame.display.set_mode((TEST_DISP_W, TEST_DISP_H))


class ModuleTest(unittest.TestCase):
def test_calculate_anchor_with_float(self):
self.assertEqual(
calculate_anchor(1.23, "x", 12345),
1.23
)

def test_calculate_anchor_centre(self):
self.assertEqual(
calculate_anchor("center", "x", 100),
50
)

def test_calculate_anchor_bottom(self):
self.assertEqual(
calculate_anchor("bottom", "y", 100),
100
)


class ActorTest(unittest.TestCase):
@classmethod
def setUpClass(self):
set_root(__file__)

def test_sensible_init_defaults(self):
a = Actor("alien")

self.assertEqual(a.image, "alien")
self.assertEqual(a.topleft, (0, 0))

def test_setting_absolute_initial_pos(self):
a = Actor("alien", pos=(100, 200), anchor=("right", "bottom"))

self.assertEqual(
a.topleft,
(100 - a.width, 200 - a.height),
)

def test_setting_relative_initial_pos_topleft(self):
a = Actor("alien", topleft=(500, 500))
self.assertEqual(a.topleft, (500, 500))

def test_setting_relative_initial_pos_center(self):
a = Actor("alien", center=(500, 500))
self.assertEqual(a.center, (500, 500))

def test_setting_relative_initial_pos_bottomright(self):
a = Actor("alien", bottomright=(500, 500))
self.assertEqual(a.bottomright, (500, 500))

def test_setting_absolute_pos_and_relative_raises_typeerror(self):
with self.assertRaises(TypeError):
Actor("alien", pos=(0, 0), bottomright=(500, 500))

def test_setting_multiple_relative_pos_raises_typeerror(self):
with self.assertRaises(TypeError):
Actor("alien", topleft=(500, 500), bottomright=(600, 600))

def test_unexpected_kwargs(self):
with self.assertRaises(TypeError) as cm:
Actor("alien", toplift=(0, 0))

self.assertEqual(
cm.exception.args[0],
"Unexpected keyword argument 'toplift' (did you mean 'topleft'?)",
)

def test_set_pos_relative_to_anchor(self):
a = Actor("alien", anchor=(10, 10))
a.pos = (100, 100)
self.assertEqual(a.topleft, (90, 90))

def test_right_angle(self):
a = Actor("alien")
self.assertEqual(a.image, "alien")
self.assertEqual(a.topleft, (0, 0))
self.assertEqual(a.pos, (33.0, 46.0))
self.assertEqual(a.width, 66)
self.assertEqual(a.height, 92)
a.angle += 90.0
self.assertEqual(a.angle, 90.0)
self.assertEqual(a.topleft, (-13, 13))
self.assertEqual(a.pos, (33.0, 46.0))
self.assertEqual(a.width, 92)
self.assertEqual(a.height, 66)

def test_rotation(self):
"""The pos of the actor must not drift with continued small rotation."""
a = Actor('alien', pos=(100.0, 100.0))
for _ in range(360):
a.angle += 1.0
self.assertEqual(a.pos, (100.0, 100.0))

def test_dir_correct(self):
"""Everything returned by dir should be indexable as an attribute."""
a = Actor("alien")
for attribute in dir(a):
a.__getattr__(attribute)
import unittest

import pygame

from pgzero.actor import calculate_anchor, Actor
from pgzero.loaders import set_root


TEST_MODULE = "pgzero.actor"
TEST_DISP_W, TEST_DISP_H = (200, 100)


class ModuleTest(unittest.TestCase):
def test_calculate_anchor_with_float(self):
self.assertEqual(
calculate_anchor(1.23, "x", 12345),
1.23
)

def test_calculate_anchor_centre(self):
self.assertEqual(
calculate_anchor("center", "x", 100),
50
)

def test_calculate_anchor_bottom(self):
self.assertEqual(
calculate_anchor("bottom", "y", 100),
100
)


class ActorTest(unittest.TestCase):
@classmethod
def setUpClass(self):
pygame.init()
pygame.display.set_mode((TEST_DISP_W, TEST_DISP_H))
set_root(__file__)

@classmethod
def tearDownClass(self):
pygame.display.quit()

def test_sensible_init_defaults(self):
a = Actor("alien")

self.assertEqual(a.image, "alien")
self.assertEqual(a.topleft, (0, 0))

def test_setting_absolute_initial_pos(self):
a = Actor("alien", pos=(100, 200), anchor=("right", "bottom"))

self.assertEqual(
a.topleft,
(100 - a.width, 200 - a.height),
)

def test_setting_relative_initial_pos_topleft(self):
a = Actor("alien", topleft=(500, 500))
self.assertEqual(a.topleft, (500, 500))

def test_setting_relative_initial_pos_center(self):
a = Actor("alien", center=(500, 500))
self.assertEqual(a.center, (500, 500))

def test_setting_relative_initial_pos_bottomright(self):
a = Actor("alien", bottomright=(500, 500))
self.assertEqual(a.bottomright, (500, 500))

def test_setting_absolute_pos_and_relative_raises_typeerror(self):
with self.assertRaises(TypeError):
Actor("alien", pos=(0, 0), bottomright=(500, 500))

def test_setting_multiple_relative_pos_raises_typeerror(self):
with self.assertRaises(TypeError):
Actor("alien", topleft=(500, 500), bottomright=(600, 600))

def test_unexpected_kwargs(self):
with self.assertRaises(TypeError) as cm:
Actor("alien", toplift=(0, 0))

self.assertEqual(
cm.exception.args[0],
"Unexpected keyword argument 'toplift' (did you mean 'topleft'?)",
)

def test_set_pos_relative_to_anchor(self):
a = Actor("alien", anchor=(10, 10))
a.pos = (100, 100)
self.assertEqual(a.topleft, (90, 90))

def test_right_angle(self):
a = Actor("alien")
self.assertEqual(a.image, "alien")
self.assertEqual(a.topleft, (0, 0))
self.assertEqual(a.pos, (33.0, 46.0))
self.assertEqual(a.width, 66)
self.assertEqual(a.height, 92)
a.angle += 90.0
self.assertEqual(a.angle, 90.0)
self.assertEqual(a.topleft, (-13, 13))
self.assertEqual(a.pos, (33.0, 46.0))
self.assertEqual(a.width, 92)
self.assertEqual(a.height, 66)

def test_rotation(self):
"""The pos of the actor must not drift with continued small rotation."""
a = Actor('alien', pos=(100.0, 100.0))
for _ in range(360):
a.angle += 1.0
self.assertEqual(a.pos, (100.0, 100.0))

def test_dir_correct(self):
"""Everything returned by dir should be indexable as an attribute."""
a = Actor("alien")
for attribute in dir(a):
a.__getattr__(attribute)
23 changes: 23 additions & 0 deletions test/test_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Tests for Pygame Zero's runner system.
This module is also a Pygame Zero game so that we can run it with pgzero.
"""
import sys
import unittest
from pathlib import Path

from pgzero.runner import load_and_run
from pgzero import clock

game_tests = Path(__file__).parent / 'game_tests'


class RunnerTest(unittest.TestCase):
"""Test that we can load and run the current file."""

def test_run(self):
"""We can load and run a game saved as UTF-8."""
clock.schedule_unique(sys.exit, 0.05)
with self.assertRaises(SystemExit):
load_and_run(str(game_tests / 'utf8.py'))
17 changes: 11 additions & 6 deletions test/test_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
from pgzero.screen import Screen
from pgzero.loaders import set_root, images

pygame.init()
surf = pygame.display.set_mode((200, 100))


class ScreenTest(unittest.TestCase):
@classmethod
def setUpClass(self):
def setUpClass(cls):
"""Initialise the display and set loaders to target the current dir."""
pygame.init()
cls.surf = pygame.display.set_mode((200, 100))
set_root(__file__)

@classmethod
def tearDownClass(cls):
"""Shut down the display."""
pygame.display.quit()

def setUp(self):
self.screen = Screen(surf)
self.screen = Screen(self.surf)
self.screen.clear()

def assertImagesAlmostEqual(self, a, b):
Expand All @@ -29,7 +34,7 @@ def assertImagesAlmostEqual(self, a, b):
def test_blit_surf(self):
"""We can blit a surface to the screen."""
self.screen.blit(images.alien, (0, 0))
self.assertImagesAlmostEqual(surf, images.expected_alien_blit)
self.assertImagesAlmostEqual(self.surf, images.expected_alien_blit)

def test_blit_name(self):
"""screen.blit() accepts an image name instead of a Surface."""
Expand Down

0 comments on commit 97c989d

Please sign in to comment.