all repos — mgba @ 0de8f432ecbfa778591aa8db92ee551e48d7a2db

mGBA Game Boy Advance Emulator

src/platform/python/cinema/test.py (view raw)

  1import os
  2import os.path
  3import mgba.core
  4import mgba.image
  5import cinema.movie
  6import itertools
  7import glob
  8import re
  9import yaml
 10from copy import deepcopy
 11from cinema import VideoFrame
 12from cinema.util import dict_merge
 13
 14
 15class CinemaTest(object):
 16    TEST = 'test.(mvl|gb|gba|nds)'
 17
 18    def __init__(self, path, root, settings={}):
 19        self.full_path = path or []
 20        self.path = os.path.abspath(os.path.join(root, *self.full_path))
 21        self.root = root
 22        self.name = '.'.join(path)
 23        self.settings = settings
 24        try:
 25            with open(os.path.join(self.path, 'manifest.yml'), 'r') as f:
 26                dict_merge(self.settings, yaml.safe_load(f))
 27        except IOError:
 28            pass
 29        self.tests = {}
 30
 31    def __repr__(self):
 32        return '<%s %s>' % (self.__class__.__name__, self.name)
 33
 34    def setup(self):
 35        results = [f for f in glob.glob(os.path.join(self.path, 'test.*')) if re.search(self.TEST, f)]
 36        self.core = mgba.core.load_path(results[0])
 37        if 'config' in self.settings:
 38            self.config = mgba.core.Config(defaults=self.settings['config'])
 39            self.core.load_config(self.config)
 40            self.core.reset()
 41
 42    def add_test(self, name, cls=None, settings={}):
 43        cls = cls or self.__class__
 44        new_settings = deepcopy(self.settings)
 45        dict_merge(new_settings, settings)
 46        self.tests[name] = cls(self.full_path + [name], self.root, new_settings)
 47        return self.tests[name]
 48
 49    def output_settings(self):
 50        output_settings = {}
 51        if 'frames' in self.settings:
 52            output_settings['limit'] = self.settings['frames']
 53        if 'skip' in self.settings:
 54            output_settings['skip'] = self.settings['skip']
 55        return output_settings
 56
 57    def __lt__(self, other):
 58        return self.path < other.path
 59
 60
 61class VideoTest(CinemaTest):
 62    BASELINE = 'baseline_%04u.png'
 63
 64    def setup(self):
 65        super(VideoTest, self).setup()
 66        self.tracer = cinema.movie.Tracer(self.core)
 67
 68    def generate_frames(self):
 69        for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())):
 70            try:
 71                baseline = VideoFrame.load(os.path.join(self.path, self.BASELINE % i))
 72                yield baseline, frame, VideoFrame.diff(baseline, frame)
 73            except IOError:
 74                yield None, frame, (None, None)
 75
 76    def test(self):
 77        self.baseline, self.frames, self.diffs = zip(*self.generate_frames())
 78        assert not any(any(diffs[0].image.convert("L").point(bool).getdata()) for diffs in self.diffs)
 79
 80    def generate_baseline(self):
 81        for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())):
 82            frame.save(os.path.join(self.path, self.BASELINE % i))
 83
 84
 85def gather_tests(root=os.getcwd()):
 86    tests = CinemaTest([], root)
 87    for path, _, files in os.walk(root):
 88        test = [f for f in files if re.match(CinemaTest.TEST, f)]
 89        if not test:
 90            continue
 91        prefix = os.path.commonprefix([path, root])
 92        suffix = path[len(prefix)+1:]
 93        test_path = suffix.split(os.sep)
 94        test_root = tests
 95        for component in test_path[:-1]:
 96            new_test = test_root.tests.get(component)
 97            if not new_test:
 98                new_test = test_root.add_test(component)
 99            test_root = new_test
100        test_root.add_test(test_path[-1], VideoTest)
101    return tests