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

nielstron / quantulum3 / 946

pending completion
946

cron

travis-ci-com

nielstron
Merge branch 'dev'

467 of 467 new or added lines in 14 files covered. (100.0%)

1812 of 1847 relevant lines covered (98.11%)

4.89 hits per line

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

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

7
import json
5✔
8
import os
5✔
9
import re
5✔
10
import unittest
5✔
11
from pathlib import Path
5✔
12
from typing import Dict, List
5✔
13

14
from .. import classes as cls
5✔
15
from .. import language, load
5✔
16
from .. import parser as p
5✔
17

18
try:
5✔
19
    import wikipedia
5✔
20
except ImportError:
5✔
21
    wikipedia = None
5✔
22

23

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

28

29
def get_classifier_path(lang) -> Path:
5✔
30
    return Path(TOPDIR).parent / "_lang" / lang / "clf.joblib"
5✔
31

32

33
def get_unit_entity_paths(unit_entity: str, lang: str) -> List[Path]:
5✔
34
    """
35
    Get the paths to the unit or entity files for a given language
36

37
    Parameters
38
    ----------
39
    unit_entity : str
40
        "units" or "entities"
41
    lang : str
42
        Language code
43
    """
44
    paths: List[Path] = [
5✔
45
        Path(TOPDIR).parent / "_lang" / lang / f"{unit_entity}.json",
46
        Path(TOPDIR).parent / f"{unit_entity}.json",
47
    ]
48

49
    return [path for path in paths if path.exists()]
5✔
50

51

52
def get_unit_paths(lang) -> List[Path]:
5✔
53
    return get_unit_entity_paths("units", lang)
5✔
54

55

56
def get_entity_paths(lang) -> List[Path]:
5✔
57
    return get_unit_entity_paths("entities", lang)
5✔
58

59

60
def multilang(funct_or_langs):
5✔
61
    """
62
    Wrapper to make a unittest test several languages
63
    :param funct_or_langs: Function to test all languages or a set of languages
64
                           to test
65
    :return:
66
    """
67

68
    # The actual wrapper
69
    def multilang_(funct):
5✔
70
        def multilang_test(*args, **kwargs):
5✔
71
            print()
5✔
72
            # Allow for someone to call the method with one explicit language
73
            if "lang" in kwargs:
5✔
74
                langs_ = [kwargs["lang"]]
5✔
75
                kwargs.pop("lang")
5✔
76
            else:
77
                langs_ = langs
5✔
78

79
            # Execute the test for all the supplied languages
80
            for lang in langs_:
5✔
81
                print("lang={}".format(lang))
5✔
82
                funct(*args, lang=lang, **kwargs)
5✔
83

84
        return multilang_test
5✔
85

86
    # Decide whether we *are* the wrapper or are to create it
87
    if callable(funct_or_langs):
5✔
88
        langs = language.LANGUAGES
5✔
89
        return multilang_(funct_or_langs)
5✔
90

91
    langs = funct_or_langs
5✔
92
    return multilang_
5✔
93

94

95
def add_type_equalities(testcase):
5✔
96
    """
97
    Add handcrafted equality functions for quantulum specific types
98
    :param testcase:
99
    :return:
100
    """
101

102
    def quantity_equality_func(first, second, msg=None):
5✔
103
        if first != second:
5✔
104
            if not msg:
5✔
105
                diffs = {"value", "surface", "span", "uncertainty"}
5✔
106
                for diff in diffs:
5✔
107
                    firstval = getattr(first, diff)
5✔
108
                    secondval = getattr(second, diff)
5✔
109
                    if firstval != secondval:
5✔
110
                        msg = (
5✔
111
                            "Quantities {first} and {second} are differing "
112
                            'in attribute "{attribute}":'
113
                            "{firstval} != {secondval}"
114
                        )
115
                        msg = msg.format(
5✔
116
                            attribute=diff,
117
                            firstval=firstval,
118
                            secondval=secondval,
119
                            first=first,
120
                            second=second,
121
                        )
122
                        break
5✔
123
            if not msg:
5✔
124
                if first.unit != second.unit:
5✔
125
                    msg = "Quantity units are differing:\n{}\n{}".format(
5✔
126
                        first.unit.__dict__, second.unit.__dict__
127
                    )
128
            raise testcase.failureException(msg)
5✔
129

130
    testcase.addTypeEqualityFunc(cls.Quantity, quantity_equality_func)
5✔
131

132

133
###############################################################################
134
def wiki_test(page="CERN", lang="en_US"):  # pragma: no cover
135
    """
136
    Download a wikipedia page and test the parser on its content.
137
    A test, designed for a human's look.
138
    Pages full of units:
139
        CERN
140
        Hubble_Space_Telescope,
141
        Herschel_Space_Observatory
142
    """
143
    if not wikipedia:
144
        print("Cannot activate wiki_test. Please install the package wikipedia first.")
145
        return
146

147
    wikipedia.set_lang(lang)
148
    content = wikipedia.page(page).content
149
    parsed = p.parse(content, lang=lang)
150
    parts = int(round(len(content) * 1.0 / 1000))
151

152
    print()
153
    end_char = 0
154
    for num, chunk in enumerate(range(parts)):
155
        os.system("clear")
156
        print()
157
        qua = [j for j in parsed if chunk * 1000 < j.span[0] < (chunk + 1) * 1000]
158
        beg_char = max(chunk * 1000, end_char)
159
        if qua:
160
            end_char = max((chunk + 1) * 1000, qua[-1].span[1])
161
            text = content[beg_char:end_char]
162
            shift = 0
163
            for quantity in qua:
164
                index = quantity.span[1] - beg_char + shift
165
                to_add = COLOR1 % (" {" + str(quantity) + "}")
166
                text = text[0:index] + to_add + COLOR2 % text[index:]
167
                shift += len(to_add) + len(COLOR2) - 6
168
        else:
169
            text = content[beg_char : (chunk + 1) * 1000]
170
        print(COLOR2 % text)
171
        print()
172
        try:
173
            input("--------- End part %d of %d\n" % (num + 1, parts))
174
        except (KeyboardInterrupt, EOFError):
175
            return
176

177

178
###############################################################################
179
def load_quantity_tests(ambiguity=True, lang="en_US"):
5✔
180
    """
181
    Load all tests from quantities.json.
182
    """
183

184
    path = language.topdir(lang).joinpath(
5✔
185
        "tests", "quantities.ambiguity.json" if ambiguity else "quantities.json"
186
    )
187
    with path.open("r", encoding="UTF-8") as testfile:
5✔
188
        tests = json.load(testfile)
5✔
189

190
    for test in tests:
5✔
191
        res = []
5✔
192
        for item in test["res"]:
5✔
193
            try:
5✔
194
                unit = load.units(lang).names[item["unit"]]
5✔
195
            except KeyError:
5✔
196
                try:
5✔
197
                    entity = item["entity"]
5✔
198
                except KeyError:  # pragma: no cover
199
                    print(
200
                        (
201
                            'Could not find %s, provide "derived" and'
202
                            ' "entity"' % item["unit"]
203
                        )
204
                    )
205
                    return
206
                if entity == "unknown":
5✔
207
                    derived = [
5✔
208
                        {
209
                            "base": load.units(lang).names[i["base"]].entity.name,
210
                            "power": i["power"],
211
                        }
212
                        for i in item["dimensions"]
213
                    ]
214
                    entity = cls.Entity(name="unknown", dimensions=derived)
5✔
215
                elif entity in load.entities(lang).names:
5✔
216
                    entity = load.entities(lang).names[entity]
5✔
217
                else:  # pragma: no cover
218
                    print(
219
                        (
220
                            'Could not find %s, provide "derived" and'
221
                            ' "entity"' % item["unit"]
222
                        )
223
                    )
224
                    return
225
                unit = cls.Unit(
5✔
226
                    name=item["unit"],
227
                    dimensions=item.get("dimensions", []),
228
                    entity=entity,
229
                    lang=lang,
230
                )
231
            try:
5✔
232
                # TODO be aware that there may never be two identical units in
233
                # a req string
234
                span = next(re.finditer(re.escape(item["surface"]), test["req"])).span()
5✔
235
            except StopIteration:  # pragma: no cover
236
                print('Surface mismatch for "%s"' % test["req"])
237
                return
238
            uncert = None
5✔
239
            if "uncertainty" in item:
5✔
240
                uncert = item["uncertainty"]
5✔
241
            res.append(
5✔
242
                cls.Quantity(
243
                    value=item["value"],
244
                    unit=unit,
245
                    surface=item["surface"],
246
                    span=span,
247
                    uncertainty=uncert,
248
                    lang=lang,
249
                )
250
            )
251
        test["res"] = [i for i in res]
5✔
252

253
    return tests
5✔
254

255

256
###############################################################################
257
def load_expand_tests(lang="en_US") -> List[Dict[str, str]]:
5✔
258
    with language.topdir(lang).joinpath("tests", "expand.json").open(
5✔
259
        "r", encoding="utf-8"
260
    ) as testfile:
261
        tests = json.load(testfile)
5✔
262
    return tests
5✔
263

264

265
###############################################################################
266
def load_error_tests(lang="en_US") -> List[str]:
5✔
267
    with language.topdir(lang).joinpath("tests", "errors.json").open(
5✔
268
        "r", encoding="utf-8"
269
    ) as testfile:
270
        tests = json.load(testfile)
5✔
271
    return tests
5✔
272

273

274
###############################################################################
275
class SetupTest(unittest.TestCase):
5✔
276
    """Test suite for the quantulum3 project."""
277

278
    def setUp(self):
5✔
279
        add_type_equalities(self)
5✔
280
        load.clear_caches()
5✔
281

282
    @multilang
5✔
283
    def test_load_tests(self, lang="en_US"):
5✔
284
        """Test that loading tests works"""
285
        self.assertIsNotNone(load_quantity_tests(True, lang))
5✔
286
        self.assertIsNotNone(load_quantity_tests(False, lang))
5✔
287
        self.assertIsNotNone(load_expand_tests(lang))
5✔
288
        self.assertIsNotNone(load_error_tests(lang))
5✔
289

290
    # The following two tests test the equality testing setup
291

292
    @unittest.expectedFailure
5✔
293
    def test_quantity_comparison_fail_unit(self):
3✔
294
        """Test unequal units (differing only in their entity)"""
295
        self.assertEqual(
5✔
296
            cls.Quantity(1, cls.Unit(name="water", entity=cls.Entity("water"))),
297
            cls.Quantity(1, cls.Unit(name="air", entity=cls.Entity("air"))),
298
        )
299

300
    @unittest.expectedFailure
5✔
301
    def test_quantity_comparison_fail_value(self):
3✔
302
        """Test unequal units (differing only in their value)"""
303
        self.assertEqual(
5✔
304
            cls.Quantity(1, cls.Unit(name="water", entity=cls.Entity("water"))),
305
            cls.Quantity(2, cls.Unit(name="water", entity=cls.Entity("water"))),
306
        )
307

308
    def test_unsupported_language(self):
5✔
309
        """Test if unknown langugage fails"""
310
        try:
5✔
311
            p.parse("Urgh wooo ddaa eeee!", lang="xx")
5✔
312
            self.fail("No error was thrown on unsupported language")  # pragma: no cover
313
        except NotImplementedError:
5✔
314
            pass
5✔
315

316
    def test_custom_unit(self):
5✔
317
        """Test if custom units work"""
318
        load.add_custom_unit(name="schlurp", surfaces=["slp"], entity="dimensionless")
5✔
319
        self.assertTrue(load.USE_ADDITIONAL_UNITS)
5✔
320
        r = p.parse("This extremely sharp tool is precise up to 0.5 slp")
5✔
321
        self.assertEqual(
5✔
322
            r[0].unit.name, "schlurp", "Custom unit was not added correctly"
323
        )
324

325
        load.remove_custom_unit(name="schlurp")
5✔
326
        r = p.parse("This extremely sharp tool is precise up to 0.5 schlurp")
5✔
327
        self.assertNotEqual(
5✔
328
            r[0].unit.name, "schlurp", "Custom unit was not removed correctly"
329
        )
330

331
    def test_custom_entity(self):
5✔
332
        """Test if custom units work"""
333
        load.add_custom_entity(name="crazy new test entity")
5✔
334
        load.add_custom_unit(
5✔
335
            name="schlurp", surfaces=["slp"], entity="crazy new test entity"
336
        )
337
        self.assertTrue(load.USE_ADDITIONAL_ENTITIES)
5✔
338
        p.parse("This extremely sharp tool is precise up to 0.5 slp")
5✔
339
        # would throw an error when trying to create the custom unit
340

341
        try:
5✔
342
            load.remove_custom_entity(name="crazy new test entity")
5✔
343
            p.parse("This extremely sharp tool is precise up to 0.5 schlurp")
5✔
344
            self.fail("Custom entity was not correctly removed")
×
345
        except KeyError:
5✔
346
            pass
5✔
347

348
    @multilang(["en_US"])
5✔
349
    def test_common_words(self, lang):
3✔
350
        """Test that the build script has run correctly (*might* fail locally)"""
351
        # Read raw 4 letter file
352
        words = language.get("load", lang).build_common_words()
5✔
353
        built = language.get("load", lang).COMMON_WORDS
5✔
354
        for length, word_list in built.items():
5✔
355
            self.assertEqual(
5✔
356
                words[length],
357
                word_list,
358
                "Build script has not been run since change to critical files",
359
            )
360

361

362
###############################################################################
363
if __name__ == "__main__":  # pragma: no cover
364
    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