"""Tests for the songbook compilation.""" # pylint: disable=too-few-public-methods import glob import logging import os import sys import subprocess import unittest import yaml from patacrep.files import path2posix, chdir from patacrep.songbook import prepare_songbook from patacrep.build import SongbookBuilder from .. import logging_reduced from .. import dynamic # pylint: disable=unused-import LOGGER = logging.getLogger(__name__) class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest): """Test of songbook compilation. For any given `foo.yaml`, it performs several checks: - the corresponding tex file is generated; - the generated tex file matches the `foo.tex.control` control file; - the compilation (tex, pdf, indexes) works without errors. This class does nothing by itself, but its metaclass populates it with test methods testing parser and renderers. """ maxDiff = None @classmethod def _iter_testmethods(cls): """Iterate over dynamically generated test methods.""" for songbook in sorted(glob.glob(os.path.join( os.path.dirname(__file__), '*.yaml', ))): base = os.path.splitext(songbook)[0] yield ( "test_latex_generation_{}".format(os.path.basename(base)), cls._create_generation_test(base), ) yield ( "test_pdf_compilation_{}".format(os.path.basename(base)), cls._create_compilation_test(base), ) for songbook in sorted(glob.glob(os.path.join( os.path.dirname(__file__), 'onthefly', '*.yaml', ))): base = os.path.splitext(songbook)[0] yield ( "test_latex_generation_onthefly_{}".format(os.path.basename(base)), cls._create_generation_test(base, True), ) yield ( "test_pdf_compilation_onthefly_{}".format(os.path.basename(base)), cls._create_compilation_test(base, True), ) @classmethod def _create_generation_test(cls, base, onthefly=False): """Return a function testing that `base.tex` is correctly generated.""" def test_generation(self): """Test that `base.tex` is correctly generated.""" songbook = "{}.yaml".format(base) # Check tex generation if onthefly: self.compile_songbook_onthefly(base, ['tex']) else: self.assertEqual(0, self.compile_songbook(songbook, "tex")) # Check generated tex control = "{}.tex.control".format(base) if not os.path.exists(control): raise unittest.SkipTest('No control file for {}'.format(songbook)) tex = "{}.tex".format(base) with open(control, mode="r", encoding="utf8") as expectfile: with open(tex, mode="r", encoding="utf8") as latexfile: expected = expectfile.read().strip() expected = expected.replace( "@TEST_FOLDER@", path2posix(os.path.dirname(__file__)), ) expected = expected.replace( "@DATA_FOLDER@", path2posix( subprocess.check_output( [sys.executable, "-c", 'import patacrep; print(patacrep.__DATADIR__)'], # pylint: disable=line-too-long universal_newlines=True, cwd=os.path.dirname(songbook), ).strip() ), ) self.assertMultiLineEqual( latexfile.read().strip(), expected, ) test_generation.__doc__ = ( "Test that '{base}' is correctly generated." ).format(base=os.path.basename(base)) return test_generation @classmethod def _create_compilation_test(cls, base, onthefly=False): """Return a function testing that `base.tex` is correctly compiled.""" @unittest.skipIf('TRAVIS' in os.environ, "Travis does not support lualatex compilation yet") def test_compilation(self): """Test that `base` is rendered to pdf.""" # Check compilation songbook = "{}.yaml".format(base) if onthefly: self.compile_songbook_onthefly(base) else: self.assertEqual(0, self.compile_songbook(songbook)) test_compilation.__doc__ = ( "Test that '{base}' is correctly compiled." ).format(base=os.path.basename(base)) return test_compilation @staticmethod def compile_songbook(songbook, steps=None): """Compile songbook, and return the command return code.""" command = [sys.executable, '-m', 'patacrep.songbook', '--cache=no', songbook] if steps: command.extend(['--steps', steps]) # Continuous Integration will be verbose if 'CI' in os.environ: command.append('-v') try: subprocess.check_call( command, stderr=subprocess.STDOUT, universal_newlines=True, cwd=os.path.dirname(songbook), ) return 0 except subprocess.CalledProcessError as error: LOGGER.warning(error.output) return error.returncode @staticmethod def compile_songbook_onthefly(base, steps=None): """Compile songbook "on the fly": without a physical songbook file.""" with open(base + ".yaml", mode="r", encoding="utf8") as sbfile: sbyaml = yaml.load(sbfile) outputdir = os.path.dirname(base) outputname = os.path.basename(base) songbook = prepare_songbook(sbyaml, outputdir, outputname, datadir_prefix=outputdir) songbook['_error'] = "fix" songbook['_cache'] = True sb_builder = SongbookBuilder(songbook) sb_builder.unsafe = True with chdir(outputdir): # Continuous Integration will be verbose if 'CI' in os.environ: with logging_reduced(level=logging.DEBUG): sb_builder.build_steps(steps) else: sb_builder.build_steps(steps)