• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nielstron / quantulum3 / 859

pending completion
859

Pull #214

travis-ci-com

web-flow
Merge 366953be3 into 96ed0b0c5
Pull Request #214: Feat/property based test

21 of 21 new or added lines in 1 file covered. (100.0%)

1376 of 1435 relevant lines covered (95.89%)

2.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.77
/quantulum3/tests/test_setup.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""
3✔
4
:mod:`Quantulum` tests.
5
"""
6

7
import json
3✔
8
import os
3✔
9
import re
3✔
10
import unittest
3✔
11
from typing import Dict, List
3✔
12

13
from .. import classes as cls
3✔
14
from .. import language, load
3✔
15
from .. import parser as p
3✔
16

17
try:
3✔
18
    import wikipedia
3✔
19
except ImportError:
3✔
20
    wikipedia = None
3✔
21

22

23
COLOR1 = "\033[94m%s\033[0m"
3✔
24
COLOR2 = "\033[91m%s\033[0m"
3✔
25
TOPDIR = os.path.dirname(__file__) or "."
3✔
26

27

28
def multilang(funct_or_langs):
3✔
29
    """
30
    Wrapper to make a unittest test several languages
31
    :param funct_or_langs: Function to test all languages or a set of languages
32
                           to test
33
    :return:
34
    """
35

36
    # The actual wrapper
37
    def multilang_(funct):
3✔
38
        def multilang_test(*args, **kwargs):
3✔
39
            print()
3✔
40
            # Allow for someone to call the method with one explicit language
41
            if "lang" in kwargs:
3✔
42
                langs_ = [kwargs["lang"]]
×
43
                kwargs.pop("lang")
×
44
            else:
45
                langs_ = langs
3✔
46

47
            # Execute the test for all the supplied languages
48
            for lang in langs_:
3✔
49
                print("lang={}".format(lang))
3✔
50
                funct(*args, lang=lang, **kwargs)
3✔
51

52
        return multilang_test
3✔
53

54
    # Decide whether we *are* the wrapper or are to create it
55
    if callable(funct_or_langs):
3✔
56
        langs = language.LANGUAGES
3✔
57
        return multilang_(funct_or_langs)
3✔
58

59
    langs = funct_or_langs
3✔
60
    return multilang_
3✔
61

62

63
def add_type_equalities(testcase):
3✔
64
    """
65
    Add handcrafted equality functions for quantulum specific types
66
    :param testcase:
67
    :return:
68
    """
69

70
    def quantity_equality_func(first, second, msg=None):
3✔
71
        if first != second:
3✔
72
            if not msg:
3✔
73
                diffs = {"value", "surface", "span", "uncertainty"}
3✔
74
                for diff in diffs:
3✔
75
                    firstval = getattr(first, diff)
3✔
76
                    secondval = getattr(second, diff)
3✔
77
                    if firstval != secondval:
3✔
78
                        msg = (
3✔
79
                            "Quantities {first} and {second} are differing "
80
                            'in attribute "{attribute}":'
81
                            "{firstval} != {secondval}"
82
                        )
83
                        msg = msg.format(
3✔
84
                            attribute=diff,
85
                            firstval=firstval,
86
                            secondval=secondval,
87
                            first=first,
88
                            second=second,
89
                        )
90
                        break
×
91
            if not msg:
3✔
92
                if first.unit != second.unit:
3✔
93
                    msg = "Quantity units are differing:\n{}\n{}".format(
3✔
94
                        first.unit.__dict__, second.unit.__dict__
95
                    )
96
            raise testcase.failureException(msg)
3✔
97

98
    testcase.addTypeEqualityFunc(cls.Quantity, quantity_equality_func)
3✔
99

100

101
###############################################################################
102
def wiki_test(page="CERN", lang="en_US"):  # pragma: no cover
103
    """
104
    Download a wikipedia page and test the parser on its content.
105
    A test, designed for a human's look.
106
    Pages full of units:
107
        CERN
108
        Hubble_Space_Telescope,
109
        Herschel_Space_Observatory
110
    """
111
    if not wikipedia:
112
        print("Cannot activate wiki_test. Please install the package wikipedia first.")
113
        return
114

115
    wikipedia.set_lang(lang)
116
    content = wikipedia.page(page).content
117
    parsed = p.parse(content, lang=lang)
118
    parts = int(round(len(content) * 1.0 / 1000))
119

120
    print()
121
    end_char = 0
122
    for num, chunk in enumerate(range(parts)):
123
        os.system("clear")
124
        print()
125
        qua = [j for j in parsed if chunk * 1000 < j.span[0] < (chunk + 1) * 1000]
126
        beg_char = max(chunk * 1000, end_char)
127
        if qua:
128
            end_char = max((chunk + 1) * 1000, qua[-1].span[1])
129
            text = content[beg_char:end_char]
130
            shift = 0
131
            for quantity in qua:
132
                index = quantity.span[1] - beg_char + shift
133
                to_add = COLOR1 % (" {" + str(quantity) + "}")
134
                text = text[0:index] + to_add + COLOR2 % text[index:]
135
                shift += len(to_add) + len(COLOR2) - 6
136
        else:
137
            text = content[beg_char : (chunk + 1) * 1000]
138
        print(COLOR2 % text)
139
        print()
140
        try:
141
            input("--------- End part %d of %d\n" % (num + 1, parts))
142
        except (KeyboardInterrupt, EOFError):
143
            return
144

145

146
###############################################################################
147
def load_quantity_tests(ambiguity=True, lang="en_US"):
3✔
148
    """
149
    Load all tests from quantities.json.
150
    """
151

152
    path = language.topdir(lang).joinpath(
3✔
153
        "tests", "quantities.ambiguity.json" if ambiguity else "quantities.json"
154
    )
155
    with path.open("r", encoding="UTF-8") as testfile:
3✔
156
        tests = json.load(testfile)
3✔
157

158
    for test in tests:
3✔
159
        res = []
3✔
160
        for item in test["res"]:
3✔
161
            try:
3✔
162
                unit = load.units(lang).names[item["unit"]]
3✔
163
            except KeyError:
3✔
164
                try:
3✔
165
                    entity = item["entity"]
3✔
166
                except KeyError:  # pragma: no cover
167
                    print(
168
                        (
169
                            'Could not find %s, provide "derived" and'
170
                            ' "entity"' % item["unit"]
171
                        )
172
                    )
173
                    return
174
                if entity == "unknown":
3✔
175
                    derived = [
3✔
176
                        {
177
                            "base": load.units(lang).names[i["base"]].entity.name,
178
                            "power": i["power"],
179
                        }
180
                        for i in item["dimensions"]
181
                    ]
182
                    entity = cls.Entity(name="unknown", dimensions=derived)
3✔
183
                elif entity in load.entities(lang).names:
3✔
184
                    entity = load.entities(lang).names[entity]
3✔
185
                else:  # pragma: no cover
186
                    print(
187
                        (
188
                            'Could not find %s, provide "derived" and'
189
                            ' "entity"' % item["unit"]
190
                        )
191
                    )
192
                    return
193
                unit = cls.Unit(
3✔
194
                    name=item["unit"],
195
                    dimensions=item.get("dimensions", []),
196
                    entity=entity,
197
                    lang=lang,
198
                )
199
            try:
3✔
200
                # TODO be aware that there may never be two identical units in
201
                # a req string
202
                span = next(re.finditer(re.escape(item["surface"]), test["req"])).span()
3✔
203
            except StopIteration:  # pragma: no cover
204
                print('Surface mismatch for "%s"' % test["req"])
205
                return
206
            uncert = None
3✔
207
            if "uncertainty" in item:
3✔
208
                uncert = item["uncertainty"]
3✔
209
            res.append(
3✔
210
                cls.Quantity(
211
                    value=item["value"],
212
                    unit=unit,
213
                    surface=item["surface"],
214
                    span=span,
215
                    uncertainty=uncert,
216
                    lang=lang,
217
                )
218
            )
219
        test["res"] = [i for i in res]
3✔
220

221
    return tests
3✔
222

223

224
###############################################################################
225
def load_expand_tests(lang="en_US") -> List[Dict[str, str]]:
3✔
226
    with language.topdir(lang).joinpath("tests", "expand.json").open(
3✔
227
        "r", encoding="utf-8"
228
    ) as testfile:
229
        tests = json.load(testfile)
3✔
230
    return tests
3✔
231

232

233
###############################################################################
234
def load_error_tests(lang="en_US") -> List[str]:
3✔
235
    with language.topdir(lang).joinpath("tests", "errors.json").open(
3✔
236
        "r", encoding="utf-8"
237
    ) as testfile:
238
        tests = json.load(testfile)
3✔
239
    return tests
3✔
240

241

242
###############################################################################
243
class SetupTest(unittest.TestCase):
3✔
244
    """Test suite for the quantulum3 project."""
245

246
    def setUp(self):
3✔
247
        add_type_equalities(self)
3✔
248

249
    @multilang
3✔
250
    def test_load_tests(self, lang="en_US"):
3✔
251
        """Test that loading tests works"""
252
        self.assertIsNotNone(load_quantity_tests(True, lang))
3✔
253
        self.assertIsNotNone(load_quantity_tests(False, lang))
3✔
254
        self.assertIsNotNone(load_expand_tests(lang))
3✔
255
        self.assertIsNotNone(load_error_tests(lang))
3✔
256

257
    # The following two tests test the equality testing setup
258

259
    @unittest.expectedFailure
3✔
260
    def test_quantity_comparison_fail_unit(self):
2✔
261
        """Test unequal units (differing only in their entity)"""
262
        self.assertEqual(
3✔
263
            cls.Quantity(1, cls.Unit(name="water", entity=cls.Entity("water"))),
264
            cls.Quantity(1, cls.Unit(name="air", entity=cls.Entity("air"))),
265
        )
266

267
    @unittest.expectedFailure
3✔
268
    def test_quantity_comparison_fail_value(self):
2✔
269
        """Test unequal units (differing only in their value)"""
270
        self.assertEqual(
3✔
271
            cls.Quantity(1, cls.Unit(name="water", entity=cls.Entity("water"))),
272
            cls.Quantity(2, cls.Unit(name="water", entity=cls.Entity("water"))),
273
        )
274

275
    def test_unsupported_language(self):
3✔
276
        """Test if unknown langugage fails"""
277
        try:
3✔
278
            p.parse("Urgh wooo ddaa eeee!", lang="xx")
3✔
279
            self.fail("No error was thrown on unsupported language")  # pragma: no cover
280
        except NotImplementedError:
3✔
281
            pass
3✔
282

283
    def test_custom_unit(self):
3✔
284
        """Test if custom units work"""
285
        load.add_custom_unit(name="schlurp", surfaces=["slp"], entity="dimensionless")
3✔
286
        r = p.parse("This extremely sharp tool is precise up to 0.5 slp")
3✔
287
        self.assertEqual(
3✔
288
            r[0].unit.name, "schlurp", "Custom unit was not added correctly"
289
        )
290

291
        load.remove_custom_unit(name="schlurp")
3✔
292
        r = p.parse("This extremely sharp tool is precise up to 0.5 schlurp")
3✔
293
        self.assertNotEqual(
3✔
294
            r[0].unit.name, "schlurp", "Custom unit was not removed correctly"
295
        )
296

297
    def test_custom_entity(self):
3✔
298
        """Test if custom units work"""
299
        load.add_custom_entity(name="crazy new test entity")
3✔
300
        load.add_custom_unit(
3✔
301
            name="schlurp", surfaces=["slp"], entity="crazy new test entity"
302
        )
303
        p.parse("This extremely sharp tool is precise up to 0.5 slp")
3✔
304
        # would throw an error when trying to create the custom unit
305

306
        try:
3✔
307
            load.remove_custom_entity(name="crazy new test entity")
3✔
308
            p.parse("This extremely sharp tool is precise up to 0.5 schlurp")
3✔
309
            self.fail("Custom entity was not correctly removed")
×
310
        except KeyError:
3✔
311
            pass
3✔
312

313
    @multilang(["en_US"])
3✔
314
    def test_common_words(self, lang):
2✔
315
        """Test that the build script has run correctly (*might* fail locally)"""
316
        # Read raw 4 letter file
317
        words = language.get("load", lang).build_common_words()
3✔
318
        built = language.get("load", lang).COMMON_WORDS
3✔
319
        for length, word_list in built.items():
3✔
320
            self.assertEqual(
3✔
321
                words[length],
322
                word_list,
323
                "Build script has not been run since change to critical files",
324
            )
325

326

327
###############################################################################
328
if __name__ == "__main__":  # pragma: no cover
329

330
    unittest.main()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc