"""Cross-port contract: Python/ctypes and Kotlin/JNI consume the same fixtures.""" import ctypes import json import math import os from pathlib import Path import unittest from dataclasses import replace from protocan.plot import Bounds, Marker, Markers, PlotMath, Viewport @unittest.skipUnless(os.environ.get("SETPROTOCOL_LIBRARY"), "Set SETPROTOCOL_LIBRARY to the built C library") class PlotTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.core = PlotMath(ctypes.CDLL(os.environ["SETPROTOCOL_LIBRARY"])) def test_shared_numeric_fixtures(self): path = Path(__file__).resolve().parents[2] / "c/set-protocol/tests/fixtures/plot-v1.json" for case in json.loads(path.read_text())["cases"]: with self.subTest(case=case["name"]): if case["output"] is None: with self.assertRaises(ValueError): self.core.call(case["op"], *case["input"]) else: actual = self.core.call(case["op"], *case["input"]) self.assertEqual(len(actual), len(case["output"])) for a, b in zip(actual, case["output"]): self.assertAlmostEqual(a, b, places=10) def test_markers_stay_in_data_coordinates_and_cross(self): bounds = Bounds(1000, 2000, -10, 10) markers = Markers().positioned(self.core, bounds) self.assertTrue(markers.enabled(Marker.A)) self.assertFalse(markers.enabled(Marker.C)) self.assertTrue(replace(markers, x_pairs=2).enabled(Marker.C)) zoomed = bounds.visible(self.core, Viewport(.25, .25, .5, .5)) self.assertEqual(markers, markers.positioned(self.core, zoomed)) moved = markers.drag(self.core, Marker.A, 50, 500, zoomed) self.assertAlmostEqual(moved.a, markers.a + 50) self.assertEqual(markers.b, moved.b) self.assertEqual(Marker.A, moved.hit(self.core, 0, 50, 500, 100, 20, zoomed)) crossed = markers.move(Marker.A, 1900).move(Marker.B, 1100) self.assertEqual(-800, self.core.delta(crossed.a, crossed.b)) def test_invalid_numeric_inputs_do_not_escape_to_painter(self): for value in (math.nan, math.inf, -math.inf): self.assertEqual(Viewport(), Viewport().transform(self.core, zoom_x=value)) with self.assertRaises(ValueError): self.core.pinch_axis(value, 10, 8) def test_locked_viewport_and_decibel_delta(self): locked = Viewport(locked=True) self.assertEqual(locked, locked.transform(self.core, zoom_x=2, pan_y=.2)) self.assertAlmostEqual(20, self.core.db_delta(1, 10)) self.assertAlmostEqual(-20, self.core.db_delta(10, 1)) self.assertIsNone(self.core.db_delta(0, 1)) if __name__ == "__main__": unittest.main()