1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3"""
4Test cases for positioning primitives.
5"""
6from zope.interface import verify
7
8from twisted.positioning import base
9from twisted.positioning.base import Angles, Directions
10from twisted.positioning.ipositioning import IPositioningBeacon
11from twisted.trial.unittest import TestCase
12
13
14class AngleTests(TestCase):
15    """
16    Tests for the L{twisted.positioning.base.Angle} class.
17    """
18
19    def test_empty(self):
20        """
21        The repr of an empty angle says that is of unknown type and unknown
22        value.
23        """
24        a = base.Angle()
25        self.assertEqual("<Angle of unknown type (unknown value)>", repr(a))
26
27    def test_variation(self):
28        """
29        The repr of an empty variation says that it is a variation of unknown
30        value.
31        """
32        a = base.Angle(angleType=Angles.VARIATION)
33        self.assertEqual("<Variation (unknown value)>", repr(a))
34
35    def test_unknownType(self):
36        """
37        The repr of an angle of unknown type but a given value displays that
38        type and value in its repr.
39        """
40        a = base.Angle(1.0)
41        self.assertEqual("<Angle of unknown type (1.0 degrees)>", repr(a))
42
43    def test_bogusType(self):
44        """
45        Trying to create an angle with a bogus type raises C{ValueError}.
46        """
47        self.assertRaises(ValueError, base.Angle, angleType="BOGUS")
48
49
50class HeadingTests(TestCase):
51    """
52    Tests for the L{twisted.positioning.base.Heading} class.
53    """
54
55    def test_simple(self):
56        """
57        Tests that a simple heading has a value in decimal degrees, which is
58        also its value when converted to a float. Its variation, and by
59        consequence its corrected heading, is L{None}.
60        """
61        h = base.Heading(1.0)
62        self.assertEqual(h.inDecimalDegrees, 1.0)
63        self.assertEqual(float(h), 1.0)
64        self.assertIsNone(h.variation)
65        self.assertIsNone(h.correctedHeading)
66
67    def test_headingWithoutVariationRepr(self):
68        """
69        A repr of a heading with no variation reports its value and that the
70        variation is unknown.
71        """
72        heading = base.Heading(1.0)
73        expectedRepr = "<Heading (1.0 degrees, unknown variation)>"
74        self.assertEqual(repr(heading), expectedRepr)
75
76    def test_headingWithVariationRepr(self):
77        """
78        A repr of a heading with known variation reports its value and the
79        value of that variation.
80        """
81        angle, variation = 1.0, -10.0
82        heading = base.Heading.fromFloats(angle, variationValue=variation)
83        reprTemplate = "<Heading ({0} degrees, <Variation ({1} degrees)>)>"
84        self.assertEqual(repr(heading), reprTemplate.format(angle, variation))
85
86    def test_valueEquality(self):
87        """
88        Headings with the same values compare equal.
89        """
90        self.assertEqual(base.Heading(1.0), base.Heading(1.0))
91
92    def test_valueInequality(self):
93        """
94        Headings with different values compare unequal.
95        """
96        self.assertNotEqual(base.Heading(1.0), base.Heading(2.0))
97
98    def test_zeroHeadingEdgeCase(self):
99        """
100        Headings can be instantiated with a value of 0 and no variation.
101        """
102        base.Heading(0)
103
104    def test_zeroHeading180DegreeVariationEdgeCase(self):
105        """
106        Headings can be instantiated with a value of 0 and a variation of 180
107        degrees.
108        """
109        base.Heading(0, 180)
110
111    def _badValueTest(self, **kw):
112        """
113        Helper function for verifying that bad values raise C{ValueError}.
114
115        @param kw: The keyword arguments passed to L{base.Heading.fromFloats}.
116        """
117        self.assertRaises(ValueError, base.Heading.fromFloats, **kw)
118
119    def test_badAngleValueEdgeCase(self):
120        """
121        Headings can not be instantiated with a value of 360 degrees.
122        """
123        self._badValueTest(angleValue=360.0)
124
125    def test_badVariationEdgeCase(self):
126        """
127        Headings can not be instantiated with a variation of -180 degrees.
128        """
129        self._badValueTest(variationValue=-180.0)
130
131    def test_negativeHeading(self):
132        """
133        Negative heading values raise C{ValueError}.
134        """
135        self._badValueTest(angleValue=-10.0)
136
137    def test_headingTooLarge(self):
138        """
139        Heading values greater than C{360.0} raise C{ValueError}.
140        """
141        self._badValueTest(angleValue=370.0)
142
143    def test_variationTooNegative(self):
144        """
145        Variation values less than C{-180.0} raise C{ValueError}.
146        """
147        self._badValueTest(variationValue=-190.0)
148
149    def test_variationTooPositive(self):
150        """
151        Variation values greater than C{180.0} raise C{ValueError}.
152        """
153        self._badValueTest(variationValue=190.0)
154
155    def test_correctedHeading(self):
156        """
157        A heading with a value and a variation has a corrected heading.
158        """
159        h = base.Heading.fromFloats(1.0, variationValue=-10.0)
160        self.assertEqual(h.correctedHeading, base.Angle(11.0, Angles.HEADING))
161
162    def test_correctedHeadingOverflow(self):
163        """
164        A heading with a value and a variation has the appropriate corrected
165        heading value, even when the variation puts it across the 360 degree
166        boundary.
167        """
168        h = base.Heading.fromFloats(359.0, variationValue=-2.0)
169        self.assertEqual(h.correctedHeading, base.Angle(1.0, Angles.HEADING))
170
171    def test_correctedHeadingOverflowEdgeCase(self):
172        """
173        A heading with a value and a variation has the appropriate corrected
174        heading value, even when the variation puts it exactly at the 360
175        degree boundary.
176        """
177        h = base.Heading.fromFloats(359.0, variationValue=-1.0)
178        self.assertEqual(h.correctedHeading, base.Angle(0.0, Angles.HEADING))
179
180    def test_correctedHeadingUnderflow(self):
181        """
182        A heading with a value and a variation has the appropriate corrected
183        heading value, even when the variation puts it under the 0 degree
184        boundary.
185        """
186        h = base.Heading.fromFloats(1.0, variationValue=2.0)
187        self.assertEqual(h.correctedHeading, base.Angle(359.0, Angles.HEADING))
188
189    def test_correctedHeadingUnderflowEdgeCase(self):
190        """
191        A heading with a value and a variation has the appropriate corrected
192        heading value, even when the variation puts it exactly at the 0
193        degree boundary.
194        """
195        h = base.Heading.fromFloats(1.0, variationValue=1.0)
196        self.assertEqual(h.correctedHeading, base.Angle(0.0, Angles.HEADING))
197
198    def test_setVariationSign(self):
199        """
200        Setting the sign of a heading changes the variation sign.
201        """
202        h = base.Heading.fromFloats(1.0, variationValue=1.0)
203        h.setSign(1)
204        self.assertEqual(h.variation.inDecimalDegrees, 1.0)
205        h.setSign(-1)
206        self.assertEqual(h.variation.inDecimalDegrees, -1.0)
207
208    def test_setBadVariationSign(self):
209        """
210        Setting the sign of a heading to values that aren't C{-1} or C{1}
211        raises C{ValueError} and does not affect the heading.
212        """
213        h = base.Heading.fromFloats(1.0, variationValue=1.0)
214        self.assertRaises(ValueError, h.setSign, -50)
215        self.assertEqual(h.variation.inDecimalDegrees, 1.0)
216
217        self.assertRaises(ValueError, h.setSign, 0)
218        self.assertEqual(h.variation.inDecimalDegrees, 1.0)
219
220        self.assertRaises(ValueError, h.setSign, 50)
221        self.assertEqual(h.variation.inDecimalDegrees, 1.0)
222
223    def test_setUnknownVariationSign(self):
224        """
225        Setting the sign on a heading with unknown variation raises
226        C{ValueError}.
227        """
228        h = base.Heading.fromFloats(1.0)
229        self.assertIsNone(h.variation.inDecimalDegrees)
230        self.assertRaises(ValueError, h.setSign, 1)
231
232
233class CoordinateTests(TestCase):
234    def test_float(self):
235        """
236        Coordinates can be converted to floats.
237        """
238        coordinate = base.Coordinate(10.0)
239        self.assertEqual(float(coordinate), 10.0)
240
241    def test_repr(self):
242        """
243        Coordinates that aren't explicitly latitudes or longitudes have an
244        appropriate repr.
245        """
246        coordinate = base.Coordinate(10.0)
247        expectedRepr = f"<Angle of unknown type ({10.0} degrees)>"
248        self.assertEqual(repr(coordinate), expectedRepr)
249
250    def test_positiveLatitude(self):
251        """
252        Positive latitudes have a repr that specifies their type and value.
253        """
254        coordinate = base.Coordinate(10.0, Angles.LATITUDE)
255        expectedRepr = f"<Latitude ({10.0} degrees)>"
256        self.assertEqual(repr(coordinate), expectedRepr)
257
258    def test_negativeLatitude(self):
259        """
260        Negative latitudes have a repr that specifies their type and value.
261        """
262        coordinate = base.Coordinate(-50.0, Angles.LATITUDE)
263        expectedRepr = f"<Latitude ({-50.0} degrees)>"
264        self.assertEqual(repr(coordinate), expectedRepr)
265
266    def test_positiveLongitude(self):
267        """
268        Positive longitudes have a repr that specifies their type and value.
269        """
270        longitude = base.Coordinate(50.0, Angles.LONGITUDE)
271        expectedRepr = f"<Longitude ({50.0} degrees)>"
272        self.assertEqual(repr(longitude), expectedRepr)
273
274    def test_negativeLongitude(self):
275        """
276        Negative longitudes have a repr that specifies their type and value.
277        """
278        longitude = base.Coordinate(-50.0, Angles.LONGITUDE)
279        expectedRepr = f"<Longitude ({-50.0} degrees)>"
280        self.assertEqual(repr(longitude), expectedRepr)
281
282    def test_bogusCoordinateType(self):
283        """
284        Creating coordinates with bogus types rasies C{ValueError}.
285        """
286        self.assertRaises(ValueError, base.Coordinate, 150.0, "BOGUS")
287
288    def test_angleTypeNotCoordinate(self):
289        """
290        Creating coordinates with angle types that aren't coordinates raises
291        C{ValueError}.
292        """
293        self.assertRaises(ValueError, base.Coordinate, 150.0, Angles.HEADING)
294
295    def test_equality(self):
296        """
297        Coordinates with the same value and type are equal.
298        """
299
300        def makeCoordinate():
301            return base.Coordinate(1.0, Angles.LONGITUDE)
302
303        self.assertEqual(makeCoordinate(), makeCoordinate())
304
305    def test_differentAnglesInequality(self):
306        """
307        Coordinates with different values aren't equal.
308        """
309        c1 = base.Coordinate(1.0)
310        c2 = base.Coordinate(-1.0)
311        self.assertNotEqual(c1, c2)
312
313    def test_differentTypesInequality(self):
314        """
315        Coordinates with the same values but different types aren't equal.
316        """
317        c1 = base.Coordinate(1.0, Angles.LATITUDE)
318        c2 = base.Coordinate(1.0, Angles.LONGITUDE)
319        self.assertNotEqual(c1, c2)
320
321    def test_sign(self):
322        """
323        Setting the sign on a coordinate sets the sign of the value of the
324        coordinate.
325        """
326        c = base.Coordinate(50.0, Angles.LATITUDE)
327        c.setSign(1)
328        self.assertEqual(c.inDecimalDegrees, 50.0)
329        c.setSign(-1)
330        self.assertEqual(c.inDecimalDegrees, -50.0)
331
332    def test_badVariationSign(self):
333        """
334        Setting a bogus sign value (not -1 or 1) on a coordinate raises
335        C{ValueError} and doesn't affect the coordinate.
336        """
337        value = 50.0
338        c = base.Coordinate(value, Angles.LATITUDE)
339
340        self.assertRaises(ValueError, c.setSign, -50)
341        self.assertEqual(c.inDecimalDegrees, 50.0)
342
343        self.assertRaises(ValueError, c.setSign, 0)
344        self.assertEqual(c.inDecimalDegrees, 50.0)
345
346        self.assertRaises(ValueError, c.setSign, 50)
347        self.assertEqual(c.inDecimalDegrees, 50.0)
348
349    def test_northernHemisphere(self):
350        """
351        Positive latitudes are in the northern hemisphere.
352        """
353        coordinate = base.Coordinate(1.0, Angles.LATITUDE)
354        self.assertEqual(coordinate.hemisphere, Directions.NORTH)
355
356    def test_easternHemisphere(self):
357        """
358        Positive longitudes are in the eastern hemisphere.
359        """
360        coordinate = base.Coordinate(1.0, Angles.LONGITUDE)
361        self.assertEqual(coordinate.hemisphere, Directions.EAST)
362
363    def test_southernHemisphere(self):
364        """
365        Negative latitudes are in the southern hemisphere.
366        """
367        coordinate = base.Coordinate(-1.0, Angles.LATITUDE)
368        self.assertEqual(coordinate.hemisphere, Directions.SOUTH)
369
370    def test_westernHemisphere(self):
371        """
372        Negative longitudes are in the western hemisphere.
373        """
374        coordinate = base.Coordinate(-1.0, Angles.LONGITUDE)
375        self.assertEqual(coordinate.hemisphere, Directions.WEST)
376
377    def test_badHemisphere(self):
378        """
379        Accessing the hemisphere for a coordinate that can't compute it
380        raises C{ValueError}.
381        """
382        coordinate = base.Coordinate(1.0, None)
383        self.assertRaises(ValueError, lambda: coordinate.hemisphere)
384
385    def test_latitudeTooLarge(self):
386        """
387        Creating a latitude with a value greater than or equal to 90 degrees
388        raises C{ValueError}.
389        """
390        self.assertRaises(ValueError, _makeLatitude, 150.0)
391        self.assertRaises(ValueError, _makeLatitude, 90.0)
392
393    def test_latitudeTooSmall(self):
394        """
395        Creating a latitude with a value less than or equal to -90 degrees
396        raises C{ValueError}.
397        """
398        self.assertRaises(ValueError, _makeLatitude, -150.0)
399        self.assertRaises(ValueError, _makeLatitude, -90.0)
400
401    def test_longitudeTooLarge(self):
402        """
403        Creating a longitude with a value greater than or equal to 180 degrees
404        raises C{ValueError}.
405        """
406        self.assertRaises(ValueError, _makeLongitude, 250.0)
407        self.assertRaises(ValueError, _makeLongitude, 180.0)
408
409    def test_longitudeTooSmall(self):
410        """
411        Creating a longitude with a value less than or equal to -180 degrees
412        raises C{ValueError}.
413        """
414        self.assertRaises(ValueError, _makeLongitude, -250.0)
415        self.assertRaises(ValueError, _makeLongitude, -180.0)
416
417    def test_inDegreesMinutesSeconds(self):
418        """
419        Coordinate values can be accessed in degrees, minutes, seconds.
420        """
421        c = base.Coordinate(50.5, Angles.LATITUDE)
422        self.assertEqual(c.inDegreesMinutesSeconds, (50, 30, 0))
423
424        c = base.Coordinate(50.213, Angles.LATITUDE)
425        self.assertEqual(c.inDegreesMinutesSeconds, (50, 12, 46))
426
427    def test_unknownAngleInDegreesMinutesSeconds(self):
428        """
429        If the vaue of a coordinate is L{None}, its values in degrees,
430        minutes, seconds is also L{None}.
431        """
432        c = base.Coordinate(None, None)
433        self.assertIsNone(c.inDegreesMinutesSeconds)
434
435
436def _makeLatitude(value):
437    """
438    Builds and returns a latitude of given value.
439    """
440    return base.Coordinate(value, Angles.LATITUDE)
441
442
443def _makeLongitude(value):
444    """
445    Builds and returns a longitude of given value.
446    """
447    return base.Coordinate(value, Angles.LONGITUDE)
448
449
450class AltitudeTests(TestCase):
451    """
452    Tests for the L{twisted.positioning.base.Altitude} class.
453    """
454
455    def test_value(self):
456        """
457        Altitudes can be instantiated and reports the correct value in
458        meters and feet, as well as when converted to float.
459        """
460        altitude = base.Altitude(1.0)
461        self.assertEqual(float(altitude), 1.0)
462        self.assertEqual(altitude.inMeters, 1.0)
463        self.assertEqual(altitude.inFeet, 1.0 / base.METERS_PER_FOOT)
464
465    def test_repr(self):
466        """
467        Altitudes report their type and value in their repr.
468        """
469        altitude = base.Altitude(1.0)
470        self.assertEqual(repr(altitude), "<Altitude (1.0 m)>")
471
472    def test_equality(self):
473        """
474        Altitudes with equal values compare equal.
475        """
476        firstAltitude = base.Altitude(1.0)
477        secondAltitude = base.Altitude(1.0)
478        self.assertEqual(firstAltitude, secondAltitude)
479
480    def test_inequality(self):
481        """
482        Altitudes with different values don't compare equal.
483        """
484        firstAltitude = base.Altitude(1.0)
485        secondAltitude = base.Altitude(-1.0)
486        self.assertNotEqual(firstAltitude, secondAltitude)
487
488
489class SpeedTests(TestCase):
490    """
491    Tests for the L{twisted.positioning.base.Speed} class.
492    """
493
494    def test_value(self):
495        """
496        Speeds can be instantiated, and report their value in meters
497        per second, and can be converted to floats.
498        """
499        speed = base.Speed(50.0)
500        self.assertEqual(speed.inMetersPerSecond, 50.0)
501        self.assertEqual(float(speed), 50.0)
502
503    def test_repr(self):
504        """
505        Speeds report their type and value in their repr.
506        """
507        speed = base.Speed(50.0)
508        self.assertEqual(repr(speed), "<Speed (50.0 m/s)>")
509
510    def test_negativeSpeeds(self):
511        """
512        Creating a negative speed raises C{ValueError}.
513        """
514        self.assertRaises(ValueError, base.Speed, -1.0)
515
516    def test_inKnots(self):
517        """
518        A speed can be converted into its value in knots.
519        """
520        speed = base.Speed(1.0)
521        self.assertEqual(1 / base.MPS_PER_KNOT, speed.inKnots)
522
523    def test_asFloat(self):
524        """
525        A speed can be converted into a C{float}.
526        """
527        self.assertEqual(1.0, float(base.Speed(1.0)))
528
529
530class ClimbTests(TestCase):
531    """
532    Tests for L{twisted.positioning.base.Climb}.
533    """
534
535    def test_simple(self):
536        """
537        Speeds can be instantiated, and report their value in meters
538        per second, and can be converted to floats.
539        """
540        climb = base.Climb(42.0)
541        self.assertEqual(climb.inMetersPerSecond, 42.0)
542        self.assertEqual(float(climb), 42.0)
543
544    def test_repr(self):
545        """
546        Climbs report their type and value in their repr.
547        """
548        climb = base.Climb(42.0)
549        self.assertEqual(repr(climb), "<Climb (42.0 m/s)>")
550
551    def test_negativeClimbs(self):
552        """
553        Climbs can have negative values, and still report that value
554        in meters per second and when converted to floats.
555        """
556        climb = base.Climb(-42.0)
557        self.assertEqual(climb.inMetersPerSecond, -42.0)
558        self.assertEqual(float(climb), -42.0)
559
560    def test_speedInKnots(self):
561        """
562        A climb can be converted into its value in knots.
563        """
564        climb = base.Climb(1.0)
565        self.assertEqual(1 / base.MPS_PER_KNOT, climb.inKnots)
566
567    def test_asFloat(self):
568        """
569        A climb can be converted into a C{float}.
570        """
571        self.assertEqual(1.0, float(base.Climb(1.0)))
572
573
574class PositionErrorTests(TestCase):
575    """
576    Tests for L{twisted.positioning.base.PositionError}.
577    """
578
579    def test_allUnset(self):
580        """
581        In an empty L{base.PositionError} with no invariant testing, all
582        dilutions of positions are L{None}.
583        """
584        positionError = base.PositionError()
585        self.assertIsNone(positionError.pdop)
586        self.assertIsNone(positionError.hdop)
587        self.assertIsNone(positionError.vdop)
588
589    def test_allUnsetWithInvariant(self):
590        """
591        In an empty L{base.PositionError} with invariant testing, all
592        dilutions of positions are L{None}.
593        """
594        positionError = base.PositionError(testInvariant=True)
595        self.assertIsNone(positionError.pdop)
596        self.assertIsNone(positionError.hdop)
597        self.assertIsNone(positionError.vdop)
598
599    def test_withoutInvariant(self):
600        """
601        L{base.PositionError}s can be instantiated with just a HDOP.
602        """
603        positionError = base.PositionError(hdop=1.0)
604        self.assertEqual(positionError.hdop, 1.0)
605
606    def test_withInvariant(self):
607        """
608        Creating a simple L{base.PositionError} with just a HDOP while
609        checking the invariant works.
610        """
611        positionError = base.PositionError(hdop=1.0, testInvariant=True)
612        self.assertEqual(positionError.hdop, 1.0)
613
614    def test_invalidWithoutInvariant(self):
615        """
616        Creating a L{base.PositionError} with values set to an impossible
617        combination works if the invariant is not checked.
618        """
619        error = base.PositionError(pdop=1.0, vdop=1.0, hdop=1.0)
620        self.assertEqual(error.pdop, 1.0)
621        self.assertEqual(error.hdop, 1.0)
622        self.assertEqual(error.vdop, 1.0)
623
624    def test_invalidWithInvariant(self):
625        """
626        Creating a L{base.PositionError} with values set to an impossible
627        combination raises C{ValueError} if the invariant is being tested.
628        """
629        self.assertRaises(
630            ValueError,
631            base.PositionError,
632            pdop=1.0,
633            vdop=1.0,
634            hdop=1.0,
635            testInvariant=True,
636        )
637
638    def test_setDOPWithoutInvariant(self):
639        """
640        You can set the PDOP value to value inconsisted with HDOP and VDOP
641        when not checking the invariant.
642        """
643        pe = base.PositionError(hdop=1.0, vdop=1.0)
644        pe.pdop = 100.0
645        self.assertEqual(pe.pdop, 100.0)
646
647    def test_setDOPWithInvariant(self):
648        """
649        Attempting to set the PDOP value to value inconsisted with HDOP and
650        VDOP when checking the invariant raises C{ValueError}.
651        """
652        pe = base.PositionError(hdop=1.0, vdop=1.0, testInvariant=True)
653        pdop = pe.pdop
654
655        def setPDOP(pe):
656            pe.pdop = 100.0
657
658        self.assertRaises(ValueError, setPDOP, pe)
659        self.assertEqual(pe.pdop, pdop)
660
661    REPR_TEMPLATE = "<PositionError (pdop: %s, hdop: %s, vdop: %s)>"
662
663    def _testDOP(self, pe, pdop, hdop, vdop):
664        """
665        Tests the DOP values in a position error, and the repr of that
666        position error.
667
668        @param pe: The position error under test.
669        @type pe: C{PositionError}
670        @param pdop: The expected position dilution of precision.
671        @type pdop: C{float} or L{None}
672        @param hdop: The expected horizontal dilution of precision.
673        @type hdop: C{float} or L{None}
674        @param vdop: The expected vertical dilution of precision.
675        @type vdop: C{float} or L{None}
676        """
677        self.assertEqual(pe.pdop, pdop)
678        self.assertEqual(pe.hdop, hdop)
679        self.assertEqual(pe.vdop, vdop)
680        self.assertEqual(repr(pe), self.REPR_TEMPLATE % (pdop, hdop, vdop))
681
682    def test_positionAndHorizontalSet(self):
683        """
684        The VDOP is correctly determined from PDOP and HDOP.
685        """
686        pdop, hdop = 2.0, 1.0
687        vdop = (pdop ** 2 - hdop ** 2) ** 0.5
688        pe = base.PositionError(pdop=pdop, hdop=hdop)
689        self._testDOP(pe, pdop, hdop, vdop)
690
691    def test_positionAndVerticalSet(self):
692        """
693        The HDOP is correctly determined from PDOP and VDOP.
694        """
695        pdop, vdop = 2.0, 1.0
696        hdop = (pdop ** 2 - vdop ** 2) ** 0.5
697        pe = base.PositionError(pdop=pdop, vdop=vdop)
698        self._testDOP(pe, pdop, hdop, vdop)
699
700    def test_horizontalAndVerticalSet(self):
701        """
702        The PDOP is correctly determined from HDOP and VDOP.
703        """
704        hdop, vdop = 1.0, 1.0
705        pdop = (hdop ** 2 + vdop ** 2) ** 0.5
706        pe = base.PositionError(hdop=hdop, vdop=vdop)
707        self._testDOP(pe, pdop, hdop, vdop)
708
709
710class BeaconInformationTests(TestCase):
711    """
712    Tests for L{twisted.positioning.base.BeaconInformation}.
713    """
714
715    def test_minimal(self):
716        """
717        For an empty beacon information object, the number of used
718        beacons is zero, the number of seen beacons is zero, and the
719        repr of the object reflects that.
720        """
721        bi = base.BeaconInformation()
722        self.assertEqual(len(bi.usedBeacons), 0)
723        expectedRepr = (
724            "<BeaconInformation (" "used beacons (0): [], " "unused beacons: [])>"
725        )
726        self.assertEqual(repr(bi), expectedRepr)
727
728    satelliteKwargs = {"azimuth": 1, "elevation": 1, "signalToNoiseRatio": 1.0}
729
730    def test_simple(self):
731        """
732        Tests a beacon information with a bunch of satellites, none of
733        which used in computing a fix.
734        """
735
736        def _buildSatellite(**kw):
737            kwargs = dict(self.satelliteKwargs)
738            kwargs.update(kw)
739            return base.Satellite(**kwargs)
740
741        beacons = set()
742        for prn in range(1, 10):
743            beacons.add(_buildSatellite(identifier=prn))
744
745        bi = base.BeaconInformation(beacons)
746
747        self.assertEqual(len(bi.seenBeacons), 9)
748        self.assertEqual(len(bi.usedBeacons), 0)
749        self.assertEqual(
750            repr(bi),
751            "<BeaconInformation (used beacons (0): [], "
752            "unused beacons: ["
753            "<Satellite (1), azimuth: 1, elevation: 1, snr: 1.0>, "
754            "<Satellite (2), azimuth: 1, elevation: 1, snr: 1.0>, "
755            "<Satellite (3), azimuth: 1, elevation: 1, snr: 1.0>, "
756            "<Satellite (4), azimuth: 1, elevation: 1, snr: 1.0>, "
757            "<Satellite (5), azimuth: 1, elevation: 1, snr: 1.0>, "
758            "<Satellite (6), azimuth: 1, elevation: 1, snr: 1.0>, "
759            "<Satellite (7), azimuth: 1, elevation: 1, snr: 1.0>, "
760            "<Satellite (8), azimuth: 1, elevation: 1, snr: 1.0>, "
761            "<Satellite (9), azimuth: 1, elevation: 1, snr: 1.0>"
762            "])>",
763        )
764
765    def test_someSatellitesUsed(self):
766        """
767        Tests a beacon information with a bunch of satellites, some of
768        them used in computing a fix.
769        """
770        bi = base.BeaconInformation()
771
772        for prn in range(1, 10):
773            satellite = base.Satellite(identifier=prn, **self.satelliteKwargs)
774            bi.seenBeacons.add(satellite)
775            if prn % 2:
776                bi.usedBeacons.add(satellite)
777
778        self.assertEqual(len(bi.seenBeacons), 9)
779        self.assertEqual(len(bi.usedBeacons), 5)
780
781        self.assertEqual(
782            repr(bi),
783            "<BeaconInformation (used beacons (5): ["
784            "<Satellite (1), azimuth: 1, elevation: 1, snr: 1.0>, "
785            "<Satellite (3), azimuth: 1, elevation: 1, snr: 1.0>, "
786            "<Satellite (5), azimuth: 1, elevation: 1, snr: 1.0>, "
787            "<Satellite (7), azimuth: 1, elevation: 1, snr: 1.0>, "
788            "<Satellite (9), azimuth: 1, elevation: 1, snr: 1.0>], "
789            "unused beacons: ["
790            "<Satellite (2), azimuth: 1, elevation: 1, snr: 1.0>, "
791            "<Satellite (4), azimuth: 1, elevation: 1, snr: 1.0>, "
792            "<Satellite (6), azimuth: 1, elevation: 1, snr: 1.0>, "
793            "<Satellite (8), azimuth: 1, elevation: 1, snr: 1.0>])>",
794        )
795
796
797class PositioningBeaconTests(TestCase):
798    """
799    Tests for L{base.PositioningBeacon}.
800    """
801
802    def test_interface(self):
803        """
804        Tests that L{base.PositioningBeacon} implements L{IPositioningBeacon}.
805        """
806        implements = IPositioningBeacon.implementedBy(base.PositioningBeacon)
807        self.assertTrue(implements)
808        verify.verifyObject(IPositioningBeacon, base.PositioningBeacon(1))
809
810    def test_repr(self):
811        """
812        Tests the repr of a positioning beacon.
813        """
814        self.assertEqual(repr(base.PositioningBeacon("A")), "<Beacon (A)>")
815
816
817class SatelliteTests(TestCase):
818    """
819    Tests for L{twisted.positioning.base.Satellite}.
820    """
821
822    def test_minimal(self):
823        """
824        Tests a minimal satellite that only has a known PRN.
825
826        Tests that the azimuth, elevation and signal to noise ratios
827        are L{None} and verifies the repr.
828        """
829        s = base.Satellite(1)
830        self.assertEqual(s.identifier, 1)
831        self.assertIsNone(s.azimuth)
832        self.assertIsNone(s.elevation)
833        self.assertIsNone(s.signalToNoiseRatio)
834        self.assertEqual(
835            repr(s), "<Satellite (1), azimuth: None, " "elevation: None, snr: None>"
836        )
837
838    def test_simple(self):
839        """
840        Tests a minimal satellite that only has a known PRN.
841
842        Tests that the azimuth, elevation and signal to noise ratios
843        are correct and verifies the repr.
844        """
845        s = base.Satellite(
846            identifier=1, azimuth=270.0, elevation=30.0, signalToNoiseRatio=25.0
847        )
848
849        self.assertEqual(s.identifier, 1)
850        self.assertEqual(s.azimuth, 270.0)
851        self.assertEqual(s.elevation, 30.0)
852        self.assertEqual(s.signalToNoiseRatio, 25.0)
853        self.assertEqual(
854            repr(s), "<Satellite (1), azimuth: 270.0, " "elevation: 30.0, snr: 25.0>"
855        )
856