summaryrefslogtreecommitdiff
path: root/conftest.py
blob: 86c213ac4f563866132b6e45dd79e6f82ed2a035 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""py.test plugin configuration."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import logging
from pathlib import Path

import pytest

import scss
from scss.compiler import compile_file
import scss.config
from scss.errors import SassEvaluationError
from scss.errors import SassMissingDependency
from scss.extension.core import CoreExtension
from scss.extension.extra import ExtraExtension
from scss.extension.fonts import FontsExtension
from scss.extension.compass import CompassExtension

try:
    import fontforge
except ImportError:
    fontforge = None


# Turn on pyscss's logging
console = logging.StreamHandler()
logger = logging.getLogger('scss')
logger.setLevel(logging.ERROR)
logger.addHandler(console)


def pytest_addoption(parser):
    """Add options for filtering which file tests run.

    This has to be done in the project root; py.test doesn't (and can't)
    recursively look for conftest.py files until after it's parsed the command
    line.
    """
    parser.addoption(
        '--include-ruby',
        help='run tests imported from Ruby and sassc, most of which fail',
        action='store_true',
        dest='include_ruby',
    )


def pytest_ignore_collect(path, config):
    # Ruby/sassc tests don't even exist without this option
    if path.basename in ('from_ruby', 'from-sassc'):
        if not config.getoption('include_ruby'):
            return True


def pytest_collect_file(path, parent):
    if path.ext == '.scss':
        parts = str(path).split(path.sep)
        # -4 tests / -3 files / -2 directory / -1 file.scss
        if parts[-4:-2] == ['tests', 'files']:
            if hasattr(SassFile, "from_parent"):
                return SassFile.from_parent(parent, fspath=path)
            else:
                return SassFile(path, parent)


class SassFile(pytest.File):
    def collect(self):
        parent_name = self.fspath.dirpath().basename
        if not fontforge and parent_name == 'fonts':
            pytest.skip("font tests require fontforge")

        if hasattr(SassItem, "from_parent"):
            yield SassItem.from_parent(parent=self, name=str(self.fspath))
        else:
            yield SassItem(str(self.fspath), self)


class SassItem(pytest.Item):
    """A Sass test input file, collected as its own test item.

    A file of the same name but with a .css extension is assumed to contain the
    expected output.
    """
    _nodeid = None

    @property
    def nodeid(self):
        # Rig the nodeid to be "directory::filename", so all the files in the
        # same directory are treated as grouped together
        if not self._nodeid:
            self._nodeid = "{0}::{1}".format(
                self.fspath.dirpath().relto(self.session.fspath),
                self.fspath.basename,
            )
        return self._nodeid

    def reportinfo(self):
        return (
            self.fspath.dirpath(),
            None,
            self.fspath.relto(self.session.fspath),
        )

    def _prunetraceback(self, excinfo):
        # Traceback implements __getitem__, but list implements __getslice__,
        # which wins in Python 2
        excinfo.traceback = excinfo.traceback.cut(__file__)

    def runtest(self):
        scss_file = Path(str(self.fspath))
        css_file = scss_file.with_suffix('.css')

        with css_file.open('rb') as fh:
            # Output is Unicode, so decode this here
            expected = fh.read().decode('utf8')

        scss.config.STATIC_ROOT = str(scss_file.parent / 'static')

        search_path = []
        include = scss_file.parent / 'include'
        if include.exists():
            search_path.append(include)
        search_path.append(scss_file.parent)

        try:
            actual = compile_file(
                scss_file,
                output_style='expanded',
                search_path=search_path,
                extensions=[
                    CoreExtension,
                    ExtraExtension,
                    FontsExtension,
                    CompassExtension,
                ],
            )
        except SassEvaluationError as e:
            # Treat any missing dependencies (PIL not installed, fontforge not
            # installed) as skips
            # TODO this is slightly cumbersome and sorta defeats the purpose of
            # having separate exceptions
            if isinstance(e.exc, SassMissingDependency):
                pytest.skip(e.format_message())
            else:
                raise

        # Normalize leading and trailing newlines
        actual = actual.strip('\n')
        expected = expected.strip('\n')

        assert expected == actual