source: pyromaths/trunk/fuentes/src/pyromaths/ex/test.py @ 423

Last change on this file since 423 was 423, checked in by mabarracus, 4 years ago

add sources from pyromaths 15.10

File size: 7.7 KB
Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2015 -- Louis Paternault (spalax@gresille.org)
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
20"""Test of exercises.
21
22This module gather tests from all exercises. Running:
23
24    python -m unittest discover
25
26does just as expected.
27"""
28
29import codecs
30import glob
31import logging
32import os
33import random
34import shutil
35import tempfile
36import unittest
37
38import pyromaths
39from pyromaths.outils import System
40
41# Logging configuration
42logging.basicConfig(level=logging.INFO)
43LOGGER = logging.getLogger()
44
45def load_tests(*__args, **__kwargs):
46    """Return an `unittest.TestSuite` containing tests from all exercises."""
47    tests = TestPerformer()
48    return tests.as_unittest_suite([
49        (exo, tests.get_tested_seeds(exo))
50        for exo in tests.iter_id()
51        ])
52
53class TestException(Exception):
54    """Generic exception for this module."""
55    pass
56
57class ExerciseNotFound(TestException):
58    """Name of exercise cannot be found in known exercises."""
59
60    def __init__(self, exercise):
61        super(ExerciseNotFound, self).__init__()
62        self.exercise = exercise
63
64    def __str__(self):
65        return "Exercise '{}' not found.".format(self.exercise)
66
67def test_path(dirlevel, name, seed, choice):
68    """Return the path of file containing expected test result."""
69    return os.path.join(
70        pyromaths.Values.data_dir(),
71        'ex',
72        dirlevel,
73        'tests',
74        "%s.%s.%s" % (name, seed, choice)
75        )
76
77class TestExercise(object):
78    """Test of an exercise"""
79
80    def __init__(self, exercise, seed):
81        self.exercise = exercise
82        self.seed = seed
83
84    def show(self):
85        """Compile exercise, and display its result."""
86        self.compile(openpdf=1)
87
88    def get_exercise(self):
89        """Return an instanciated exercise."""
90        random.seed(self.seed)
91        return self.exercise()
92
93    def compile(self, openpdf=0, movefile=False, pipe=None):
94        """Compile exercise"""
95        tempdir = tempfile.mkdtemp()
96
97        old_dir = os.path.abspath(os.getcwd())
98        System.creation({
99            'creer_pdf': True,
100            'creer_unpdf': True,
101            'titre': u"Fiche de révisions",
102            'corrige': True,
103            'niveau': "test",
104            'nom_fichier': u'test.tex',
105            'chemin_fichier': tempdir,
106            'fiche_exo': os.path.join(tempdir, 'exercises.tex'),
107            'fiche_cor': os.path.join(tempdir, 'exercises-corrige.tex'),
108            'datadir': pyromaths.Values.data_dir(),
109            'configdir': pyromaths.Values.configdir(),
110            'modele': 'pyromaths.tex',
111            'liste_exos': [self.get_exercise()],
112            'les_fiches': pyromaths.Values.lesfiches(),
113            'openpdf': openpdf,
114            'pipe': pipe,
115        })
116        os.chdir(old_dir)
117
118        if movefile:
119            destname = "{}-{}.pdf".format(
120                self.exercise.id(),
121                self.seed,
122                )
123            shutil.move(
124                os.path.join(tempdir, 'exercises.pdf'),
125                destname,
126            )
127        else:
128            destname = os.path.join(tempdir, 'exercises.pdf')
129
130        return destname
131
132    def test_path(self, name):
133        """Return the path of the file containing expected results."""
134        return test_path(
135            self.exercise.dirlevel,
136            self.exercise.name(),
137            self.seed,
138            name,
139            )
140
141    def write(self):
142        """Write expected test results."""
143        exo = self.get_exercise()
144        with codecs.open(self.test_path("statement"), "w", "utf8") as statement:
145            statement.write(u"\n".join(exo.tex_statement()))
146        with codecs.open(self.test_path("answer"), "w", "utf8") as answer:
147            answer.write(u"\n".join(exo.tex_answer()))
148
149    def read(self, choice):
150        """Read expected test result."""
151        with codecs.open(self.test_path(choice), "r", "utf8") as file:
152            return file.read()
153
154    def remove(self):
155        """Remove test"""
156        os.remove(self.test_path("statement"))
157        os.remove(self.test_path("answer"))
158
159    def changed(self):
160        """Return `True` iff exercise has changed."""
161        exo = self.get_exercise()
162        if "\n".join(exo.tex_statement()) != self.read('statement'):
163            return True
164        if "\n".join(exo.tex_answer()) != self.read('answer'):
165            return True
166        return False
167
168class UnittestExercise(unittest.TestCase):
169    """Test an exercise, with a particular seed."""
170
171    maxDiff = None
172
173    def __init__(self, exercise=None):
174        super(UnittestExercise, self).__init__()
175        self.exercise = exercise
176
177    def shortDescription(self):
178        if self.exercise is None:
179            return super(UnittestExercise, self).shortDescription()
180        else:
181            return self.exercise.exercise.id()
182
183    def runTest(self):
184        """Perform test"""
185        exo = self.exercise.get_exercise()
186
187        self.assertEqual(
188            u"\n".join(exo.tex_statement()),
189            self.exercise.read('statement'),
190            )
191
192        self.assertEqual(
193            u"\n".join(exo.tex_answer()),
194            self.exercise.read('answer'),
195            )
196
197class TestPerformer(object):
198    """Perform tests over every exercises"""
199
200    def __init__(self):
201        self.exercises = {}
202        levels = pyromaths.ex.load_levels()
203        for level in levels:
204            for exercise in levels[level]:
205                self.exercises[exercise.id()] = exercise
206
207    def iter_id(self):
208        """Iterate over exercise ids."""
209        return self.exercises.keys()
210
211    def get(self, exercise, seed):
212        """Return the `TestExercise` object corresponding to the arguments."""
213        if exercise not in self.exercises:
214            raise KeyError(exercise)
215        return TestExercise(self.exercises[exercise], seed)
216
217    def get_tested_seeds(self, exercise):
218        """Return seeds that are tested for this exercise"""
219        if exercise not in self.exercises:
220            raise ExerciseNotFound(exercise)
221        statement_seeds = [
222            os.path.basename(path).split(".")[1]
223            for path in glob.glob(test_path(
224                self.exercises[exercise].dirlevel,
225                self.exercises[exercise].name(),
226                '*',
227                'statement'
228                ))
229            ]
230        for seed in statement_seeds:
231            if os.path.exists(test_path(
232                    self.exercises[exercise].dirlevel,
233                    self.exercises[exercise].name(),
234                    seed,
235                    'answer',
236                )):
237                yield int(seed)
238
239    def iter_missing(self):
240        """Iterate over exercises that are not tested."""
241        for exercise in self.exercises:
242            if not list(self.get_tested_seeds(exercise)):
243                yield exercise
244
245    def as_unittest_suite(self, exercises):
246        """Return the tests, as a `unittest.TestSuite`."""
247        suite = unittest.TestSuite()
248        for exercise, seeds in exercises:
249            for seed in seeds:
250                suite.addTest(UnittestExercise(exercise=self.get(exercise, seed)))
251        return suite
Note: See TracBrowser for help on using the repository browser.