Source code for autowisp.tests.test_diagnostic_types

"""Tests for the diagnostic vocabulary.

The catalogue was moved out of ``_init_diagnostic_types()`` so that an
expression can be validated without opening a project.  The risk that
creates is drift: the seeder and the catalogue are now two things that
must agree, where before they were one.  :class:`TestSeeding` is the test
that keeps them honest, and is the reason the rest of this file exists.
"""

import tempfile
import unittest

from autowisp.database.initialize_database import _init_diagnostic_types
from autowisp.database.interface import set_project_home, start_db_session

# pylint: disable=no-name-in-module
from autowisp.database.data_model import DiagnosticType

# pylint: enable=no-name-in-module
from autowisp.diagnostics.diagnostic_types import (
    is_diagnostic,
    is_known_quantity,
    is_quantile_diagnostic,
    standard_diagnostic_names,
    standard_diagnostic_types,
    time_quantity,
)


[docs] class TestCatalogue(unittest.TestCase): """The shape and content of the static catalogue."""
[docs] def test_it_cannot_be_mutated(self): """A shared catalogue one caller can edit is not shared, it is a bug.""" with self.assertRaises(TypeError): standard_diagnostic_types()["invented"] = "nonsense" self.assertIsInstance(standard_diagnostic_names(), frozenset)
[docs] def test_names_are_exactly_the_mapping_keys(self): """The two accessors cannot disagree about what exists.""" self.assertEqual( standard_diagnostic_names(), set(standard_diagnostic_types()) )
[docs] def test_descriptions_are_fully_interpolated(self): """No description may contain an unexpanded placeholder. Guards a real defect the extraction fixed: the continuation line of the ``*_map_residual`` description was not an f-string, so every one of them was seeded reading "and smoothed {param.upper()} map" literally. A missing ``f`` prefix is silent, so only a test of the rendered text catches it coming back. """ for name, description in standard_diagnostic_types().items(): with self.subTest(diagnostic=name): self.assertNotIn("{", description) self.assertTrue(description.strip())
[docs] def test_it_is_not_empty(self): """A catalogue that silently emptied would pass every other test.""" self.assertGreater(len(standard_diagnostic_types()), 10)
[docs] class TestRuntimePatterns(unittest.TestCase): """The names that are described rather than listed. ``is_quantile_diagnostic`` is the single definition of what a quantile is called: ``_save_image_diagnostics`` asks it before creating a row, and expression validation asks it before accepting a name. The two used to disagree -- a loose ``startswith("pixel_q")`` against an anchored pattern -- which is exactly the drift one predicate prevents. """
[docs] def test_it_matches_quantile_diagnostics(self): """``calibrate`` names them ``pixel_q`` followed by the percentile.""" for name in ("pixel_q1", "pixel_q99", "pixel_q999"): with self.subTest(name=name): self.assertTrue(is_quantile_diagnostic(name)) self.assertTrue(is_diagnostic(name))
[docs] def test_it_does_not_swallow_plausible_names(self): """The anchor and the required digits both matter. Without them ``pixel_quality`` would be taken for a quantile, and a real diagnostic by that name could then never be referenced -- an expression naming it would be told it is shadowing something. """ for name in ("pixel_quality", "pixel_q", "pixel_q9x", "bg_center"): with self.subTest(name=name): self.assertFalse(is_quantile_diagnostic(name))
[docs] class TestVocabulary(unittest.TestCase): """What an expression may reference, without opening any project."""
[docs] def test_catalogue_entries_are_diagnostics(self): """The seeded names, recorded or not in any particular project.""" for name in ("bg_center", "astrom_residual", "diagonal_fov"): with self.subTest(name=name): self.assertTrue(is_diagnostic(name))
[docs] def test_time_is_a_quantity_but_not_a_diagnostic(self): """``jd`` is a column of the image row, not an ``image_diagnostics`` value, yet it is a variable in the same flat name space.""" self.assertFalse(is_diagnostic(time_quantity)) self.assertTrue(is_known_quantity(time_quantity))
[docs] def test_invented_names_are_neither(self): """The vocabulary is closed, which is what makes it checkable.""" for name in ("bg_centre", "no_such_thing", ""): with self.subTest(name=name): self.assertFalse(is_known_quantity(name))
[docs] class TestSeeding(unittest.TestCase): """The catalogue and the rows it seeds must stay identical."""
[docs] def test_seeded_rows_match_the_catalogue(self): """The whole point of the extraction, and the only silent failure. Descriptions are compared as well as names: they are what the catalogue carries beyond what validation needs, so a mismatch there is exactly the drift that splitting the two invites. """ with tempfile.TemporaryDirectory() as project_home: set_project_home(project_home) _init_diagnostic_types() with start_db_session() as db_session: seeded = { row.name: row.description for row in db_session.query(DiagnosticType).all() } self.assertEqual(seeded, dict(standard_diagnostic_types()))
if __name__ == "__main__": unittest.main()