Coverage for tests/test_exprParserYacc.py: 9%
382 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-11 02:31 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-11 02:31 -0800
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""Simple unit test for exprParser subpackage module.
23"""
25import unittest
27import astropy.time
28from lsst.daf.butler.registry.queries.expressions.parser import ParseError, ParserYacc, TreeVisitor, exprTree
29from lsst.daf.butler.registry.queries.expressions.parser.parserYacc import _parseTimeString
32class _Visitor(TreeVisitor):
33 """Trivial implementation of TreeVisitor."""
35 def visitNumericLiteral(self, value, node):
36 return f"N({value})"
38 def visitStringLiteral(self, value, node):
39 return f"S({value})"
41 def visitTimeLiteral(self, value, node):
42 return f"T({value})"
44 def visitRangeLiteral(self, start, stop, stride, node):
45 if stride is None:
46 return f"R({start}..{stop})"
47 else:
48 return f"R({start}..{stop}:{stride})"
50 def visitIdentifier(self, name, node):
51 return f"ID({name})"
53 def visitUnaryOp(self, operator, operand, node):
54 return f"U({operator} {operand})"
56 def visitBinaryOp(self, operator, lhs, rhs, node):
57 return f"B({lhs} {operator} {rhs})"
59 def visitIsIn(self, lhs, values, not_in, node):
60 values = ", ".join([str(val) for val in values])
61 if not_in:
62 return f"!IN({lhs} ({values}))"
63 else:
64 return f"IN({lhs} ({values}))"
66 def visitParens(self, expression, node):
67 return f"P({expression})"
70class ParserLexTestCase(unittest.TestCase):
71 """A test case for ParserYacc"""
73 def setUp(self):
74 pass
76 def tearDown(self):
77 pass
79 def testInstantiate(self):
80 """Tests for making ParserLex instances"""
81 parser = ParserYacc() # noqa: F841
83 def testEmpty(self):
84 """Tests for empty expression"""
85 parser = ParserYacc()
87 # empty expression is allowed, returns None
88 tree = parser.parse("")
89 self.assertIsNone(tree)
91 def testParseLiteral(self):
92 """Tests for literals (strings/numbers)"""
93 parser = ParserYacc()
95 tree = parser.parse("1")
96 self.assertIsInstance(tree, exprTree.NumericLiteral)
97 self.assertEqual(tree.value, "1")
99 tree = parser.parse(".5e-2")
100 self.assertIsInstance(tree, exprTree.NumericLiteral)
101 self.assertEqual(tree.value, ".5e-2")
103 tree = parser.parse("'string'")
104 self.assertIsInstance(tree, exprTree.StringLiteral)
105 self.assertEqual(tree.value, "string")
107 tree = parser.parse("10..20")
108 self.assertIsInstance(tree, exprTree.RangeLiteral)
109 self.assertEqual(tree.start, 10)
110 self.assertEqual(tree.stop, 20)
111 self.assertEqual(tree.stride, None)
113 tree = parser.parse("-10 .. 10:5")
114 self.assertIsInstance(tree, exprTree.RangeLiteral)
115 self.assertEqual(tree.start, -10)
116 self.assertEqual(tree.stop, 10)
117 self.assertEqual(tree.stride, 5)
119 # more extensive tests of time parsing is below
120 tree = parser.parse("T'51544.0'")
121 self.assertIsInstance(tree, exprTree.TimeLiteral)
122 self.assertEqual(tree.value, astropy.time.Time(51544.0, format="mjd", scale="tai"))
124 tree = parser.parse("T'2020-03-30T12:20:33'")
125 self.assertIsInstance(tree, exprTree.TimeLiteral)
126 self.assertEqual(tree.value, astropy.time.Time("2020-03-30T12:20:33", format="isot", scale="utc"))
128 def testParseIdentifiers(self):
129 """Tests for identifiers"""
130 parser = ParserYacc()
132 tree = parser.parse("a")
133 self.assertIsInstance(tree, exprTree.Identifier)
134 self.assertEqual(tree.name, "a")
136 tree = parser.parse("a.b")
137 self.assertIsInstance(tree, exprTree.Identifier)
138 self.assertEqual(tree.name, "a.b")
140 def testParseParens(self):
141 """Tests for identifiers"""
142 parser = ParserYacc()
144 tree = parser.parse("(a)")
145 self.assertIsInstance(tree, exprTree.Parens)
146 self.assertIsInstance(tree.expr, exprTree.Identifier)
147 self.assertEqual(tree.expr.name, "a")
149 def testUnaryOps(self):
150 """Tests for unary plus and minus"""
151 parser = ParserYacc()
153 tree = parser.parse("+a")
154 self.assertIsInstance(tree, exprTree.UnaryOp)
155 self.assertEqual(tree.op, "+")
156 self.assertIsInstance(tree.operand, exprTree.Identifier)
157 self.assertEqual(tree.operand.name, "a")
159 tree = parser.parse("- x.y")
160 self.assertIsInstance(tree, exprTree.UnaryOp)
161 self.assertEqual(tree.op, "-")
162 self.assertIsInstance(tree.operand, exprTree.Identifier)
163 self.assertEqual(tree.operand.name, "x.y")
165 def testBinaryOps(self):
166 """Tests for binary operators"""
167 parser = ParserYacc()
169 tree = parser.parse("a + b")
170 self.assertIsInstance(tree, exprTree.BinaryOp)
171 self.assertEqual(tree.op, "+")
172 self.assertIsInstance(tree.lhs, exprTree.Identifier)
173 self.assertIsInstance(tree.rhs, exprTree.Identifier)
174 self.assertEqual(tree.lhs.name, "a")
175 self.assertEqual(tree.rhs.name, "b")
177 tree = parser.parse("a - 2")
178 self.assertIsInstance(tree, exprTree.BinaryOp)
179 self.assertEqual(tree.op, "-")
180 self.assertIsInstance(tree.lhs, exprTree.Identifier)
181 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
182 self.assertEqual(tree.lhs.name, "a")
183 self.assertEqual(tree.rhs.value, "2")
185 tree = parser.parse("2 * 2")
186 self.assertIsInstance(tree, exprTree.BinaryOp)
187 self.assertEqual(tree.op, "*")
188 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
189 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
190 self.assertEqual(tree.lhs.value, "2")
191 self.assertEqual(tree.rhs.value, "2")
193 tree = parser.parse("1.e5/2")
194 self.assertIsInstance(tree, exprTree.BinaryOp)
195 self.assertEqual(tree.op, "/")
196 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
197 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
198 self.assertEqual(tree.lhs.value, "1.e5")
199 self.assertEqual(tree.rhs.value, "2")
201 tree = parser.parse("333%76")
202 self.assertIsInstance(tree, exprTree.BinaryOp)
203 self.assertEqual(tree.op, "%")
204 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
205 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
206 self.assertEqual(tree.lhs.value, "333")
207 self.assertEqual(tree.rhs.value, "76")
209 # tests for overlaps operator
210 tree = parser.parse("region1 OVERLAPS region2")
211 self.assertIsInstance(tree, exprTree.BinaryOp)
212 self.assertEqual(tree.op, "OVERLAPS")
213 self.assertIsInstance(tree.lhs, exprTree.Identifier)
214 self.assertIsInstance(tree.rhs, exprTree.Identifier)
216 # time ranges with literals
217 tree = parser.parse("(T'2020-01-01', T'2020-01-02') overlaps (T'2020-01-01', T'2020-01-02')")
218 self.assertIsInstance(tree, exprTree.BinaryOp)
219 self.assertEqual(tree.op, "OVERLAPS")
220 self.assertIsInstance(tree.lhs, exprTree.TupleNode)
221 self.assertIsInstance(tree.rhs, exprTree.TupleNode)
223 # but syntax allows anything, it's visitor responsibility to decide
224 # what are the right operands
225 tree = parser.parse("x+y Overlaps function(x-y)")
226 self.assertIsInstance(tree, exprTree.BinaryOp)
227 self.assertEqual(tree.op, "OVERLAPS")
228 self.assertIsInstance(tree.lhs, exprTree.BinaryOp)
229 self.assertIsInstance(tree.rhs, exprTree.FunctionCall)
231 def testIsIn(self):
232 """Tests for IN"""
233 parser = ParserYacc()
235 tree = parser.parse("a in (1,2,'X')")
236 self.assertIsInstance(tree, exprTree.IsIn)
237 self.assertFalse(tree.not_in)
238 self.assertIsInstance(tree.lhs, exprTree.Identifier)
239 self.assertEqual(tree.lhs.name, "a")
240 self.assertIsInstance(tree.values, list)
241 self.assertEqual(len(tree.values), 3)
242 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
243 self.assertEqual(tree.values[0].value, "1")
244 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral)
245 self.assertEqual(tree.values[1].value, "2")
246 self.assertIsInstance(tree.values[2], exprTree.StringLiteral)
247 self.assertEqual(tree.values[2].value, "X")
249 tree = parser.parse("10 not in (1000, 2000..3000:100)")
250 self.assertIsInstance(tree, exprTree.IsIn)
251 self.assertTrue(tree.not_in)
252 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
253 self.assertEqual(tree.lhs.value, "10")
254 self.assertIsInstance(tree.values, list)
255 self.assertEqual(len(tree.values), 2)
256 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
257 self.assertEqual(tree.values[0].value, "1000")
258 self.assertIsInstance(tree.values[1], exprTree.RangeLiteral)
259 self.assertEqual(tree.values[1].start, 2000)
260 self.assertEqual(tree.values[1].stop, 3000)
261 self.assertEqual(tree.values[1].stride, 100)
263 tree = parser.parse("10 in (-1000, -2000)")
264 self.assertIsInstance(tree, exprTree.IsIn)
265 self.assertFalse(tree.not_in)
266 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
267 self.assertEqual(tree.lhs.value, "10")
268 self.assertIsInstance(tree.values, list)
269 self.assertEqual(len(tree.values), 2)
270 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
271 self.assertEqual(tree.values[0].value, "-1000")
272 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral)
273 self.assertEqual(tree.values[1].value, "-2000")
275 # test for time contained in time range, all literals
276 tree = parser.parse("T'2020-01-01' in (T'2020-01-01', T'2020-01-02')")
277 self.assertIsInstance(tree, exprTree.IsIn)
278 self.assertFalse(tree.not_in)
279 self.assertIsInstance(tree.lhs, exprTree.TimeLiteral)
280 self.assertEqual(len(tree.values), 2)
281 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral)
282 self.assertIsInstance(tree.values[1], exprTree.TimeLiteral)
284 # test for time range contained in time range
285 tree = parser.parse("(T'2020-01-01', t1) in (T'2020-01-01', t2)")
286 self.assertIsInstance(tree, exprTree.IsIn)
287 self.assertFalse(tree.not_in)
288 self.assertIsInstance(tree.lhs, exprTree.TupleNode)
289 self.assertEqual(len(tree.values), 2)
290 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral)
291 self.assertIsInstance(tree.values[1], exprTree.Identifier)
293 # test for point in region (we don't have region syntax yet, use
294 # identifier)
295 tree = parser.parse("point(1, 2) in (region1)")
296 self.assertIsInstance(tree, exprTree.IsIn)
297 self.assertFalse(tree.not_in)
298 self.assertIsInstance(tree.lhs, exprTree.PointNode)
299 self.assertEqual(len(tree.values), 1)
300 self.assertIsInstance(tree.values[0], exprTree.Identifier)
302 # parens on right hand side are required
303 with self.assertRaises(ParseError):
304 parser.parse("point(1, 2) in region1")
306 # and we don't support full expressions in RHS list
307 with self.assertRaises(ParseError):
308 parser.parse("point(1, 2) in (x + y)")
310 def testCompareOps(self):
311 """Tests for comparison operators"""
312 parser = ParserYacc()
314 for op in ("=", "!=", "<", "<=", ">", ">="):
315 tree = parser.parse("a {} 10".format(op))
316 self.assertIsInstance(tree, exprTree.BinaryOp)
317 self.assertEqual(tree.op, op)
318 self.assertIsInstance(tree.lhs, exprTree.Identifier)
319 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
320 self.assertEqual(tree.lhs.name, "a")
321 self.assertEqual(tree.rhs.value, "10")
323 def testBoolOps(self):
324 """Tests for boolean operators"""
325 parser = ParserYacc()
327 for op in ("OR", "AND"):
328 tree = parser.parse("a {} b".format(op))
329 self.assertIsInstance(tree, exprTree.BinaryOp)
330 self.assertEqual(tree.op, op)
331 self.assertIsInstance(tree.lhs, exprTree.Identifier)
332 self.assertIsInstance(tree.rhs, exprTree.Identifier)
333 self.assertEqual(tree.lhs.name, "a")
334 self.assertEqual(tree.rhs.name, "b")
336 tree = parser.parse("NOT b")
337 self.assertIsInstance(tree, exprTree.UnaryOp)
338 self.assertEqual(tree.op, "NOT")
339 self.assertIsInstance(tree.operand, exprTree.Identifier)
340 self.assertEqual(tree.operand.name, "b")
342 def testFunctionCall(self):
343 """Tests for function calls"""
344 parser = ParserYacc()
346 tree = parser.parse("f()")
347 self.assertIsInstance(tree, exprTree.FunctionCall)
348 self.assertEqual(tree.name, "f")
349 self.assertEqual(tree.args, [])
351 tree = parser.parse("f1(a)")
352 self.assertIsInstance(tree, exprTree.FunctionCall)
353 self.assertEqual(tree.name, "f1")
354 self.assertEqual(len(tree.args), 1)
355 self.assertIsInstance(tree.args[0], exprTree.Identifier)
356 self.assertEqual(tree.args[0].name, "a")
358 tree = parser.parse("anything_goes('a', x+y, ((a AND b) or (C = D)), NOT T < 42., Z IN (1,2,3,4))")
359 self.assertIsInstance(tree, exprTree.FunctionCall)
360 self.assertEqual(tree.name, "anything_goes")
361 self.assertEqual(len(tree.args), 5)
362 self.assertIsInstance(tree.args[0], exprTree.StringLiteral)
363 self.assertIsInstance(tree.args[1], exprTree.BinaryOp)
364 self.assertIsInstance(tree.args[2], exprTree.Parens)
365 self.assertIsInstance(tree.args[3], exprTree.UnaryOp)
366 self.assertIsInstance(tree.args[4], exprTree.IsIn)
368 with self.assertRaises(ParseError):
369 parser.parse("f.ff()")
371 def testPointNode(self):
372 """Tests for POINT() function"""
373 parser = ParserYacc()
375 # POINT function makes special node type
376 tree = parser.parse("POINT(Object.ra, 0.0)")
377 self.assertIsInstance(tree, exprTree.PointNode)
378 self.assertIsInstance(tree.ra, exprTree.Identifier)
379 self.assertEqual(tree.ra.name, "Object.ra")
380 self.assertIsInstance(tree.dec, exprTree.NumericLiteral)
381 self.assertEqual(tree.dec.value, "0.0")
383 # it is not case sensitive
384 tree = parser.parse("Point(1, 1)")
385 self.assertIsInstance(tree, exprTree.PointNode)
387 def testTupleNode(self):
388 """Tests for tuple"""
389 parser = ParserYacc()
391 # test with simple identifier and literal
392 tree = parser.parse("(Object.ra, 0.0)")
393 self.assertIsInstance(tree, exprTree.TupleNode)
394 self.assertEqual(len(tree.items), 2)
395 self.assertIsInstance(tree.items[0], exprTree.Identifier)
396 self.assertEqual(tree.items[0].name, "Object.ra")
397 self.assertIsInstance(tree.items[1], exprTree.NumericLiteral)
398 self.assertEqual(tree.items[1].value, "0.0")
400 # any expression can appear in tuple
401 tree = parser.parse("(x+y, ((a AND b) or (C = D)))")
402 self.assertIsInstance(tree, exprTree.TupleNode)
403 self.assertEqual(len(tree.items), 2)
404 self.assertIsInstance(tree.items[0], exprTree.BinaryOp)
405 self.assertIsInstance(tree.items[1], exprTree.Parens)
407 # only two items can appear in a tuple
408 with self.assertRaises(ParseError):
409 parser.parse("(1, 2, 3)")
411 def testExpression(self):
412 """Test for more or less complete expression"""
413 parser = ParserYacc()
415 expression = (
416 "((instrument='HSC' AND detector != 9) OR instrument='CFHT') "
417 "AND tract=8766 AND patch.cell_x > 5 AND "
418 "patch.cell_y < 4 AND band='i'"
419 )
421 tree = parser.parse(expression)
422 self.assertIsInstance(tree, exprTree.BinaryOp)
423 self.assertEqual(tree.op, "AND")
424 self.assertIsInstance(tree.lhs, exprTree.BinaryOp)
425 # AND is left-associative, so rhs operand will be the
426 # last sub-expressions
427 self.assertIsInstance(tree.rhs, exprTree.BinaryOp)
428 self.assertEqual(tree.rhs.op, "=")
429 self.assertIsInstance(tree.rhs.lhs, exprTree.Identifier)
430 self.assertEqual(tree.rhs.lhs.name, "band")
431 self.assertIsInstance(tree.rhs.rhs, exprTree.StringLiteral)
432 self.assertEqual(tree.rhs.rhs.value, "i")
434 def testSubstitution(self):
435 """Test for identifier substitution"""
436 # substitution is not recursive, so we can swap id2/id3
437 idMap = {
438 "id1": exprTree.StringLiteral("id1 value"),
439 "id2": exprTree.Identifier("id3"),
440 "id3": exprTree.Identifier("id2"),
441 "POINT": exprTree.StringLiteral("not used"),
442 "OR": exprTree.StringLiteral("not used"),
443 }
444 parser = ParserYacc(idMap=idMap)
446 expression = "id1 = 'v'"
447 tree = parser.parse(expression)
448 self.assertIsInstance(tree, exprTree.BinaryOp)
449 self.assertEqual(tree.op, "=")
450 self.assertIsInstance(tree.lhs, exprTree.StringLiteral)
451 self.assertEqual(tree.lhs.value, "id1 value")
453 expression = "id2 - id3"
454 tree = parser.parse(expression)
455 self.assertIsInstance(tree, exprTree.BinaryOp)
456 self.assertEqual(tree.op, "-")
457 self.assertIsInstance(tree.lhs, exprTree.Identifier)
458 self.assertEqual(tree.lhs.name, "id3")
459 self.assertIsInstance(tree.rhs, exprTree.Identifier)
460 self.assertEqual(tree.rhs.name, "id2")
462 # reserved words are not substituted
463 expression = "id2 OR id3"
464 tree = parser.parse(expression)
465 self.assertIsInstance(tree, exprTree.BinaryOp)
466 self.assertEqual(tree.op, "OR")
468 # function names are not substituted
469 expression = "POINT(1, 2)"
470 tree = parser.parse(expression)
471 self.assertIsInstance(tree, exprTree.PointNode)
473 def testException(self):
474 """Test for exceptional cases"""
476 def _assertExc(exc, expr, token, pos, lineno, posInLine):
477 """Check exception attribute values"""
478 self.assertEqual(exc.expression, expr)
479 self.assertEqual(exc.token, token)
480 self.assertEqual(exc.pos, pos)
481 self.assertEqual(exc.lineno, lineno)
482 self.assertEqual(exc.posInLine, posInLine)
484 parser = ParserYacc()
486 expression = "(1, 2, 3)"
487 with self.assertRaises(ParseError) as catcher:
488 parser.parse(expression)
489 _assertExc(catcher.exception, expression, ",", 5, 1, 5)
491 expression = "\n(1\n,\n 2, 3)"
492 with self.assertRaises(ParseError) as catcher:
493 parser.parse(expression)
494 _assertExc(catcher.exception, expression, ",", 8, 4, 2)
496 expression = "T'not-a-time'"
497 with self.assertRaises(ParseError) as catcher:
498 parser.parse(expression)
499 _assertExc(catcher.exception, expression, "not-a-time", 0, 1, 0)
501 def testStr(self):
502 """Test for formatting"""
503 parser = ParserYacc()
505 tree = parser.parse("(a+b)")
506 self.assertEqual(str(tree), "(a + b)")
508 tree = parser.parse("1 in (1,'x',3)")
509 self.assertEqual(str(tree), "1 IN (1, 'x', 3)")
511 tree = parser.parse("a not in (1,'x',3)")
512 self.assertEqual(str(tree), "a NOT IN (1, 'x', 3)")
514 tree = parser.parse("(A or B) And NoT (x+3 > y)")
515 self.assertEqual(str(tree), "(A OR B) AND NOT (x + 3 > y)")
517 tree = parser.parse("A in (100, 200..300:50)")
518 self.assertEqual(str(tree), "A IN (100, 200..300:50)")
520 def testVisit(self):
521 """Test for visitor methods"""
523 # test should cover all visit* methods
524 parser = ParserYacc()
525 visitor = _Visitor()
527 tree = parser.parse("(a+b)")
528 result = tree.visit(visitor)
529 self.assertEqual(result, "P(B(ID(a) + ID(b)))")
531 tree = parser.parse("(A or B) and not (x + 3 > y)")
532 result = tree.visit(visitor)
533 self.assertEqual(result, "B(P(B(ID(A) OR ID(B))) AND U(NOT P(B(B(ID(x) + N(3)) > ID(y)))))")
535 tree = parser.parse("x in (1,2) AND y NOT IN (1.1, .25, 1e2) OR z in ('a', 'b')")
536 result = tree.visit(visitor)
537 self.assertEqual(
538 result,
539 "B(B(IN(ID(x) (N(1), N(2))) AND !IN(ID(y) (N(1.1), N(.25), N(1e2))))"
540 " OR IN(ID(z) (S(a), S(b))))",
541 )
543 tree = parser.parse("x in (1,2,5..15) AND y NOT IN (-100..100:10)")
544 result = tree.visit(visitor)
545 self.assertEqual(result, "B(IN(ID(x) (N(1), N(2), R(5..15))) AND !IN(ID(y) (R(-100..100:10))))")
547 tree = parser.parse("time > T'2020-03-30'")
548 result = tree.visit(visitor)
549 self.assertEqual(result, "B(ID(time) > T(2020-03-30 00:00:00.000))")
551 def testParseTimeStr(self):
552 """Test for _parseTimeString method"""
554 # few expected failures
555 bad_times = [
556 "",
557 " ",
558 "123.456e10", # no exponents
559 "mjd-dj/123.456", # format can only have word chars
560 "123.456/mai-tai", # scale can only have word chars
561 "2020-03-01 00", # iso needs minutes if hour is given
562 "2020-03-01T", # isot needs hour:minute
563 "2020:100:12", # yday needs minutes if hour is given
564 "format/123456.00", # unknown format
565 "123456.00/unscale", # unknown scale
566 ]
567 for bad_time in bad_times:
568 with self.assertRaises(ValueError):
569 _parseTimeString(bad_time)
571 # each tuple is (string, value, format, scale)
572 tests = [
573 ("51544.0", 51544.0, "mjd", "tai"),
574 ("mjd/51544.0", 51544.0, "mjd", "tai"),
575 ("51544.0/tai", 51544.0, "mjd", "tai"),
576 ("mjd/51544.0/tai", 51544.0, "mjd", "tai"),
577 ("MJd/51544.0/TAi", 51544.0, "mjd", "tai"),
578 ("jd/2451544.5", 2451544.5, "jd", "tai"),
579 ("jd/2451544.5", 2451544.5, "jd", "tai"),
580 ("51544.0/utc", 51544.0, "mjd", "utc"),
581 ("unix/946684800.0", 946684800.0, "unix", "utc"),
582 ("cxcsec/63072064.184", 63072064.184, "cxcsec", "tt"),
583 ("2020-03-30", "2020-03-30 00:00:00.000", "iso", "utc"),
584 ("2020-03-30 12:20", "2020-03-30 12:20:00.000", "iso", "utc"),
585 ("2020-03-30 12:20:33.456789", "2020-03-30 12:20:33.457", "iso", "utc"),
586 ("2020-03-30T12:20", "2020-03-30T12:20:00.000", "isot", "utc"),
587 ("2020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "isot", "utc"),
588 ("isot/2020-03-30", "2020-03-30T00:00:00.000", "isot", "utc"),
589 ("2020-03-30/tai", "2020-03-30 00:00:00.000", "iso", "tai"),
590 ("+02020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"),
591 ("+02020-03-30T12:20:33", "2020-03-30T12:20:33.000", "fits", "utc"),
592 ("+02020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "fits", "utc"),
593 ("fits/2020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"),
594 ("2020:123", "2020:123:00:00:00.000", "yday", "utc"),
595 ("2020:123:12:20", "2020:123:12:20:00.000", "yday", "utc"),
596 ("2020:123:12:20:33.456789", "2020:123:12:20:33.457", "yday", "utc"),
597 ("yday/2020:123:12:20/tai", "2020:123:12:20:00.000", "yday", "tai"),
598 ]
599 for time_str, value, fmt, scale in tests:
600 time = _parseTimeString(time_str)
601 self.assertEqual(time.value, value)
602 self.assertEqual(time.format, fmt)
603 self.assertEqual(time.scale, scale)
606if __name__ == "__main__": 606 ↛ 607line 606 didn't jump to line 607, because the condition on line 606 was never true
607 unittest.main()