Traktor/myenv/Lib/site-packages/pygame/tests/time_test.py
2024-05-26 05:12:46 +02:00

411 lines
16 KiB
Python

import os
import platform
import unittest
import pygame
import time
Clock = pygame.time.Clock
class ClockTypeTest(unittest.TestCase):
__tags__ = ["timing"]
def test_construction(self):
"""Ensure a Clock object can be created"""
c = Clock()
self.assertTrue(c, "Clock cannot be constructed")
def test_get_fps(self):
"""test_get_fps tests pygame.time.get_fps()"""
# Initialization check, first call should return 0 fps
c = Clock()
self.assertEqual(c.get_fps(), 0)
# Type check get_fps should return float
self.assertTrue(type(c.get_fps()) == float)
# Allowable margin of error in percentage
delta = 0.30
# Test fps correctness for 100, 60 and 30 fps
self._fps_test(c, 100, delta)
self._fps_test(c, 60, delta)
self._fps_test(c, 30, delta)
def _fps_test(self, clock, fps, delta):
"""ticks fps times each second, hence get_fps() should return fps"""
delay_per_frame = 1.0 / fps
for f in range(fps): # For one second tick and sleep
clock.tick()
time.sleep(delay_per_frame)
# We should get around fps (+- fps*delta -- delta % of fps)
self.assertAlmostEqual(clock.get_fps(), fps, delta=fps * delta)
def test_get_rawtime(self):
iterations = 10
delay = 0.1
delay_miliseconds = delay * (10**3) # actual time difference between ticks
framerate_limit = 5
delta = 50 # allowable error in milliseconds
# Testing Clock Initialization
c = Clock()
self.assertEqual(c.get_rawtime(), 0)
# Testing Raw Time with Frame Delay
for f in range(iterations):
time.sleep(delay)
c.tick(framerate_limit)
c1 = c.get_rawtime()
self.assertAlmostEqual(delay_miliseconds, c1, delta=delta)
# Testing get_rawtime() = get_time()
for f in range(iterations):
time.sleep(delay)
c.tick()
c1 = c.get_rawtime()
c2 = c.get_time()
self.assertAlmostEqual(c1, c2, delta=delta)
@unittest.skipIf(platform.machine() == "s390x", "Fails on s390x")
@unittest.skipIf(
os.environ.get("CI", None), "CI can have variable time slices, slow."
)
def test_get_time(self):
# Testing parameters
delay = 0.1 # seconds
delay_miliseconds = delay * (10**3)
iterations = 10
delta = 50 # milliseconds
# Testing Clock Initialization
c = Clock()
self.assertEqual(c.get_time(), 0)
# Testing within delay parameter range
for i in range(iterations):
time.sleep(delay)
c.tick()
c1 = c.get_time()
self.assertAlmostEqual(delay_miliseconds, c1, delta=delta)
# Comparing get_time() results with the 'time' module
for i in range(iterations):
t0 = time.time()
time.sleep(delay)
c.tick()
t1 = time.time()
c1 = c.get_time() # elapsed time in milliseconds
d0 = (t1 - t0) * (
10**3
) #'time' module elapsed time converted to milliseconds
self.assertAlmostEqual(d0, c1, delta=delta)
@unittest.skipIf(platform.machine() == "s390x", "Fails on s390x")
@unittest.skipIf(
os.environ.get("CI", None), "CI can have variable time slices, slow."
)
def test_tick(self):
"""Tests time.Clock.tick()"""
"""
Loops with a set delay a few times then checks what tick reports to
verify its accuracy. Then calls tick with a desired frame-rate and
verifies it is not faster than the desired frame-rate nor is it taking
a dramatically long time to complete
"""
# Adjust this value to increase the acceptable sleep jitter
epsilon = 5 # 1.5
# Adjust this value to increase the acceptable locked frame-rate jitter
epsilon2 = 0.3
# adjust this value to increase the acceptable frame-rate margin
epsilon3 = 20
testing_framerate = 60
milliseconds = 5.0
collection = []
c = Clock()
# verify time.Clock.tick() will measure the time correctly
c.tick()
for i in range(100):
time.sleep(milliseconds / 1000) # convert to seconds
collection.append(c.tick())
# removes the first highest and lowest value
for outlier in [min(collection), max(collection)]:
if outlier != milliseconds:
collection.remove(outlier)
average_time = float(sum(collection)) / len(collection)
# assert the deviation from the intended frame-rate is within the
# acceptable amount (the delay is not taking a dramatically long time)
self.assertAlmostEqual(average_time, milliseconds, delta=epsilon)
# verify tick will control the frame-rate
c = Clock()
collection = []
start = time.time()
for i in range(testing_framerate):
collection.append(c.tick(testing_framerate))
# remove the highest and lowest outliers
for outlier in [min(collection), max(collection)]:
if outlier != round(1000 / testing_framerate):
collection.remove(outlier)
end = time.time()
# Since calling tick with a desired fps will prevent the program from
# running at greater than the given fps, 100 iterations at 100 fps
# should last no less than 1 second
self.assertAlmostEqual(end - start, 1, delta=epsilon2)
average_tick_time = float(sum(collection)) / len(collection)
self.assertAlmostEqual(
1000 / average_tick_time, testing_framerate, delta=epsilon3
)
def test_tick_busy_loop(self):
"""Test tick_busy_loop"""
c = Clock()
# Test whether the return value of tick_busy_loop is equal to
# (FPS is accurate) or greater than (slower than the set FPS)
# with a small margin for error based on differences in how this
# test runs in practise - it either sometimes runs slightly fast
# or seems to based on a rounding error.
second_length = 1000
shortfall_tolerance = 1 # (ms) The amount of time a tick is allowed to run short of, to account for underlying rounding errors
sample_fps = 40
self.assertGreaterEqual(
c.tick_busy_loop(sample_fps),
(second_length / sample_fps) - shortfall_tolerance,
)
pygame.time.wait(10) # incur delay between ticks that's faster than sample_fps
self.assertGreaterEqual(
c.tick_busy_loop(sample_fps),
(second_length / sample_fps) - shortfall_tolerance,
)
pygame.time.wait(200) # incur delay between ticks that's slower than sample_fps
self.assertGreaterEqual(
c.tick_busy_loop(sample_fps),
(second_length / sample_fps) - shortfall_tolerance,
)
high_fps = 500
self.assertGreaterEqual(
c.tick_busy_loop(high_fps), (second_length / high_fps) - shortfall_tolerance
)
low_fps = 1
self.assertGreaterEqual(
c.tick_busy_loop(low_fps), (second_length / low_fps) - shortfall_tolerance
)
low_non_factor_fps = 35 # 1000/35 makes 28.5714285714
frame_length_without_decimal_places = int(
second_length / low_non_factor_fps
) # Same result as math.floor
self.assertGreaterEqual(
c.tick_busy_loop(low_non_factor_fps),
frame_length_without_decimal_places - shortfall_tolerance,
)
high_non_factor_fps = 750 # 1000/750 makes 1.3333...
frame_length_without_decimal_places_2 = int(
second_length / high_non_factor_fps
) # Same result as math.floor
self.assertGreaterEqual(
c.tick_busy_loop(high_non_factor_fps),
frame_length_without_decimal_places_2 - shortfall_tolerance,
)
zero_fps = 0
self.assertEqual(c.tick_busy_loop(zero_fps), 0)
# Check behaviour of unexpected values
negative_fps = -1
self.assertEqual(c.tick_busy_loop(negative_fps), 0)
fractional_fps = 32.75
frame_length_without_decimal_places_3 = int(second_length / fractional_fps)
self.assertGreaterEqual(
c.tick_busy_loop(fractional_fps),
frame_length_without_decimal_places_3 - shortfall_tolerance,
)
bool_fps = True
self.assertGreaterEqual(
c.tick_busy_loop(bool_fps), (second_length / bool_fps) - shortfall_tolerance
)
class TimeModuleTest(unittest.TestCase):
__tags__ = ["timing"]
@unittest.skipIf(platform.machine() == "s390x", "Fails on s390x")
@unittest.skipIf(
os.environ.get("CI", None), "CI can have variable time slices, slow."
)
def test_delay(self):
"""Tests time.delay() function."""
millis = 50 # millisecond to wait on each iteration
iterations = 20 # number of iterations
delta = 150 # Represents acceptable margin of error for wait in ms
# Call checking function
self._wait_delay_check(pygame.time.delay, millis, iterations, delta)
# After timing behaviour, check argument type exceptions
self._type_error_checks(pygame.time.delay)
def test_get_ticks(self):
"""Tests time.get_ticks()"""
"""
Iterates and delays for arbitrary amount of time for each iteration,
check get_ticks to equal correct gap time
"""
iterations = 20
millis = 50
delta = 15 # Acceptable margin of error in ms
# Assert return type to be int
self.assertTrue(type(pygame.time.get_ticks()) == int)
for i in range(iterations):
curr_ticks = pygame.time.get_ticks() # Save current tick count
curr_time = time.time() # Save current time
pygame.time.delay(millis) # Delay for millis
# Time and Ticks difference from start of the iteration
time_diff = round((time.time() - curr_time) * 1000)
ticks_diff = pygame.time.get_ticks() - curr_ticks
# Assert almost equality of the ticking time and time difference
self.assertAlmostEqual(ticks_diff, time_diff, delta=delta)
@unittest.skipIf(platform.machine() == "s390x", "Fails on s390x")
@unittest.skipIf(
os.environ.get("CI", None), "CI can have variable time slices, slow."
)
def test_set_timer(self):
"""Tests time.set_timer()"""
"""
Tests if a timer will post the correct amount of eventid events in
the specified delay. Test is posting event objects work.
Also tests if setting milliseconds to 0 stops the timer and if
the once argument and repeat arguments work.
"""
pygame.init()
TIMER_EVENT_TYPE = pygame.event.custom_type()
timer_event = pygame.event.Event(TIMER_EVENT_TYPE)
delta = 50
timer_delay = 100
test_number = 8 # Number of events to read for the test
events = 0 # Events read
pygame.event.clear()
pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay)
# Test that 'test_number' events are posted in the right amount of time
t1 = pygame.time.get_ticks()
max_test_time = t1 + timer_delay * test_number + delta
while events < test_number:
for event in pygame.event.get():
if event == timer_event:
events += 1
# The test takes too much time
if pygame.time.get_ticks() > max_test_time:
break
pygame.time.set_timer(TIMER_EVENT_TYPE, 0)
t2 = pygame.time.get_ticks()
# Is the number ef events and the timing right?
self.assertEqual(events, test_number)
self.assertAlmostEqual(timer_delay * test_number, t2 - t1, delta=delta)
# Test that the timer stopped when set with 0ms delay.
pygame.time.delay(200)
self.assertNotIn(timer_event, pygame.event.get())
# Test that the old timer for an event is deleted when a new timer is set
pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay)
pygame.time.delay(int(timer_delay * 3.5))
self.assertEqual(pygame.event.get().count(timer_event), 3)
pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay * 10) # long wait time
pygame.time.delay(timer_delay * 5)
self.assertNotIn(timer_event, pygame.event.get())
pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay * 3)
pygame.time.delay(timer_delay * 7)
self.assertEqual(pygame.event.get().count(timer_event), 2)
pygame.time.set_timer(TIMER_EVENT_TYPE, timer_delay)
pygame.time.delay(int(timer_delay * 5.5))
self.assertEqual(pygame.event.get().count(timer_event), 5)
# Test that the loops=True works
pygame.time.set_timer(TIMER_EVENT_TYPE, 10, True)
pygame.time.delay(40)
self.assertEqual(pygame.event.get().count(timer_event), 1)
# Test a variety of event objects, test loops argument
events_to_test = [
pygame.event.Event(TIMER_EVENT_TYPE),
pygame.event.Event(
TIMER_EVENT_TYPE, foo="9gwz5", baz=12, lol=[124, (34, "")]
),
pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a, unicode="a"),
]
repeat = 3
millis = 50
for e in events_to_test:
pygame.time.set_timer(e, millis, loops=repeat)
pygame.time.delay(2 * millis * repeat)
self.assertEqual(pygame.event.get().count(e), repeat)
pygame.quit()
def test_wait(self):
"""Tests time.wait() function."""
millis = 100 # millisecond to wait on each iteration
iterations = 10 # number of iterations
delta = 50 # Represents acceptable margin of error for wait in ms
# Call checking function
self._wait_delay_check(pygame.time.wait, millis, iterations, delta)
# After timing behaviour, check argument type exceptions
self._type_error_checks(pygame.time.wait)
def _wait_delay_check(self, func_to_check, millis, iterations, delta):
""" "
call func_to_check(millis) "iterations" times and check each time if
function "waited" for given millisecond (+- delta). At the end, take
average time for each call (whole_duration/iterations), which should
be equal to millis (+- delta - acceptable margin of error).
*Created to avoid code duplication during delay and wait tests
"""
# take starting time for duration calculation
start_time = time.time()
for i in range(iterations):
wait_time = func_to_check(millis)
# Check equality of wait_time and millis with margin of error delta
self.assertAlmostEqual(wait_time, millis, delta=delta)
stop_time = time.time()
# Cycle duration in millisecond
duration = round((stop_time - start_time) * 1000)
# Duration/Iterations should be (almost) equal to predefined millis
self.assertAlmostEqual(duration / iterations, millis, delta=delta)
def _type_error_checks(self, func_to_check):
"""Checks 3 TypeError (float, tuple, string) for the func_to_check"""
"""Intended for time.delay and time.wait functions"""
# Those methods throw no exceptions on negative integers
self.assertRaises(TypeError, func_to_check, 0.1) # check float
self.assertRaises(TypeError, pygame.time.delay, (0, 1)) # check tuple
self.assertRaises(TypeError, pygame.time.delay, "10") # check string
###############################################################################
if __name__ == "__main__":
unittest.main()