Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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/>. 

21 

22"""Simple unit test for exprParser subpackage module. 

23""" 

24 

25import unittest 

26 

27import astropy.time 

28 

29from lsst.daf.butler.registry.queries.exprParser import exprTree, TreeVisitor, ParserYacc, ParseError 

30from lsst.daf.butler.registry.queries.exprParser.parserYacc import _parseTimeString 

31 

32 

33class _Visitor(TreeVisitor): 

34 """Trivial implementation of TreeVisitor. 

35 """ 

36 def visitNumericLiteral(self, value, node): 

37 return f"N({value})" 

38 

39 def visitStringLiteral(self, value, node): 

40 return f"S({value})" 

41 

42 def visitTimeLiteral(self, value, node): 

43 return f"T({value})" 

44 

45 def visitRangeLiteral(self, start, stop, stride, node): 

46 if stride is None: 

47 return f"R({start}..{stop})" 

48 else: 

49 return f"R({start}..{stop}:{stride})" 

50 

51 def visitIdentifier(self, name, node): 

52 return f"ID({name})" 

53 

54 def visitUnaryOp(self, operator, operand, node): 

55 return f"U({operator} {operand})" 

56 

57 def visitBinaryOp(self, operator, lhs, rhs, node): 

58 return f"B({lhs} {operator} {rhs})" 

59 

60 def visitIsIn(self, lhs, values, not_in, node): 

61 values = ", ".join([str(val) for val in values]) 

62 if not_in: 

63 return f"!IN({lhs} ({values}))" 

64 else: 

65 return f"IN({lhs} ({values}))" 

66 

67 def visitParens(self, expression, node): 

68 return f"P({expression})" 

69 

70 

71class ParserLexTestCase(unittest.TestCase): 

72 """A test case for ParserYacc 

73 """ 

74 

75 def setUp(self): 

76 pass 

77 

78 def tearDown(self): 

79 pass 

80 

81 def testInstantiate(self): 

82 """Tests for making ParserLex instances 

83 """ 

84 parser = ParserYacc() # noqa: F841 

85 

86 def testEmpty(self): 

87 """Tests for empty expression 

88 """ 

89 parser = ParserYacc() 

90 

91 # empty expression is allowed, returns None 

92 tree = parser.parse("") 

93 self.assertIsNone(tree) 

94 

95 def testParseLiteral(self): 

96 """Tests for literals (strings/numbers) 

97 """ 

98 parser = ParserYacc() 

99 

100 tree = parser.parse('1') 

101 self.assertIsInstance(tree, exprTree.NumericLiteral) 

102 self.assertEqual(tree.value, '1') 

103 

104 tree = parser.parse('.5e-2') 

105 self.assertIsInstance(tree, exprTree.NumericLiteral) 

106 self.assertEqual(tree.value, '.5e-2') 

107 

108 tree = parser.parse("'string'") 

109 self.assertIsInstance(tree, exprTree.StringLiteral) 

110 self.assertEqual(tree.value, 'string') 

111 

112 tree = parser.parse("10..20") 

113 self.assertIsInstance(tree, exprTree.RangeLiteral) 

114 self.assertEqual(tree.start, 10) 

115 self.assertEqual(tree.stop, 20) 

116 self.assertEqual(tree.stride, None) 

117 

118 tree = parser.parse("-10 .. 10:5") 

119 self.assertIsInstance(tree, exprTree.RangeLiteral) 

120 self.assertEqual(tree.start, -10) 

121 self.assertEqual(tree.stop, 10) 

122 self.assertEqual(tree.stride, 5) 

123 

124 # more extensive tests of time parsing is below 

125 tree = parser.parse("T'51544.0'") 

126 self.assertIsInstance(tree, exprTree.TimeLiteral) 

127 self.assertEqual(tree.value, astropy.time.Time(51544.0, format="mjd", scale="tai")) 

128 

129 tree = parser.parse("T'2020-03-30T12:20:33'") 

130 self.assertIsInstance(tree, exprTree.TimeLiteral) 

131 self.assertEqual(tree.value, astropy.time.Time("2020-03-30T12:20:33", format="isot", scale="utc")) 

132 

133 def testParseIdentifiers(self): 

134 """Tests for identifiers 

135 """ 

136 parser = ParserYacc() 

137 

138 tree = parser.parse('a') 

139 self.assertIsInstance(tree, exprTree.Identifier) 

140 self.assertEqual(tree.name, 'a') 

141 

142 tree = parser.parse('a.b') 

143 self.assertIsInstance(tree, exprTree.Identifier) 

144 self.assertEqual(tree.name, 'a.b') 

145 

146 def testParseParens(self): 

147 """Tests for identifiers 

148 """ 

149 parser = ParserYacc() 

150 

151 tree = parser.parse('(a)') 

152 self.assertIsInstance(tree, exprTree.Parens) 

153 self.assertIsInstance(tree.expr, exprTree.Identifier) 

154 self.assertEqual(tree.expr.name, 'a') 

155 

156 def testUnaryOps(self): 

157 """Tests for unary plus and minus 

158 """ 

159 parser = ParserYacc() 

160 

161 tree = parser.parse('+a') 

162 self.assertIsInstance(tree, exprTree.UnaryOp) 

163 self.assertEqual(tree.op, '+') 

164 self.assertIsInstance(tree.operand, exprTree.Identifier) 

165 self.assertEqual(tree.operand.name, 'a') 

166 

167 tree = parser.parse('- x.y') 

168 self.assertIsInstance(tree, exprTree.UnaryOp) 

169 self.assertEqual(tree.op, '-') 

170 self.assertIsInstance(tree.operand, exprTree.Identifier) 

171 self.assertEqual(tree.operand.name, 'x.y') 

172 

173 def testBinaryOps(self): 

174 """Tests for binary operators 

175 """ 

176 parser = ParserYacc() 

177 

178 tree = parser.parse('a + b') 

179 self.assertIsInstance(tree, exprTree.BinaryOp) 

180 self.assertEqual(tree.op, '+') 

181 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

182 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

183 self.assertEqual(tree.lhs.name, 'a') 

184 self.assertEqual(tree.rhs.name, 'b') 

185 

186 tree = parser.parse('a - 2') 

187 self.assertIsInstance(tree, exprTree.BinaryOp) 

188 self.assertEqual(tree.op, '-') 

189 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

190 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

191 self.assertEqual(tree.lhs.name, 'a') 

192 self.assertEqual(tree.rhs.value, '2') 

193 

194 tree = parser.parse('2 * 2') 

195 self.assertIsInstance(tree, exprTree.BinaryOp) 

196 self.assertEqual(tree.op, '*') 

197 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

198 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

199 self.assertEqual(tree.lhs.value, '2') 

200 self.assertEqual(tree.rhs.value, '2') 

201 

202 tree = parser.parse('1.e5/2') 

203 self.assertIsInstance(tree, exprTree.BinaryOp) 

204 self.assertEqual(tree.op, '/') 

205 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

206 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

207 self.assertEqual(tree.lhs.value, '1.e5') 

208 self.assertEqual(tree.rhs.value, '2') 

209 

210 tree = parser.parse('333%76') 

211 self.assertIsInstance(tree, exprTree.BinaryOp) 

212 self.assertEqual(tree.op, '%') 

213 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

214 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

215 self.assertEqual(tree.lhs.value, '333') 

216 self.assertEqual(tree.rhs.value, '76') 

217 

218 # tests for overlaps operator 

219 tree = parser.parse("region1 OVERLAPS region2") 

220 self.assertIsInstance(tree, exprTree.BinaryOp) 

221 self.assertEqual(tree.op, "OVERLAPS") 

222 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

223 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

224 

225 # time ranges with literals 

226 tree = parser.parse("(T'2020-01-01', T'2020-01-02') overlaps (T'2020-01-01', T'2020-01-02')") 

227 self.assertIsInstance(tree, exprTree.BinaryOp) 

228 self.assertEqual(tree.op, "OVERLAPS") 

229 self.assertIsInstance(tree.lhs, exprTree.TupleNode) 

230 self.assertIsInstance(tree.rhs, exprTree.TupleNode) 

231 

232 # but syntax allows anything, it's visitor responsibility to decide 

233 # what are the right operands 

234 tree = parser.parse("x+y Overlaps function(x-y)") 

235 self.assertIsInstance(tree, exprTree.BinaryOp) 

236 self.assertEqual(tree.op, "OVERLAPS") 

237 self.assertIsInstance(tree.lhs, exprTree.BinaryOp) 

238 self.assertIsInstance(tree.rhs, exprTree.FunctionCall) 

239 

240 def testIsIn(self): 

241 """Tests for IN 

242 """ 

243 parser = ParserYacc() 

244 

245 tree = parser.parse("a in (1,2,'X')") 

246 self.assertIsInstance(tree, exprTree.IsIn) 

247 self.assertFalse(tree.not_in) 

248 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

249 self.assertEqual(tree.lhs.name, 'a') 

250 self.assertIsInstance(tree.values, list) 

251 self.assertEqual(len(tree.values), 3) 

252 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral) 

253 self.assertEqual(tree.values[0].value, '1') 

254 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral) 

255 self.assertEqual(tree.values[1].value, '2') 

256 self.assertIsInstance(tree.values[2], exprTree.StringLiteral) 

257 self.assertEqual(tree.values[2].value, 'X') 

258 

259 tree = parser.parse("10 not in (1000, 2000..3000:100)") 

260 self.assertIsInstance(tree, exprTree.IsIn) 

261 self.assertTrue(tree.not_in) 

262 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

263 self.assertEqual(tree.lhs.value, '10') 

264 self.assertIsInstance(tree.values, list) 

265 self.assertEqual(len(tree.values), 2) 

266 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral) 

267 self.assertEqual(tree.values[0].value, '1000') 

268 self.assertIsInstance(tree.values[1], exprTree.RangeLiteral) 

269 self.assertEqual(tree.values[1].start, 2000) 

270 self.assertEqual(tree.values[1].stop, 3000) 

271 self.assertEqual(tree.values[1].stride, 100) 

272 

273 tree = parser.parse("10 in (-1000, -2000)") 

274 self.assertIsInstance(tree, exprTree.IsIn) 

275 self.assertFalse(tree.not_in) 

276 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral) 

277 self.assertEqual(tree.lhs.value, '10') 

278 self.assertIsInstance(tree.values, list) 

279 self.assertEqual(len(tree.values), 2) 

280 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral) 

281 self.assertEqual(tree.values[0].value, '-1000') 

282 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral) 

283 self.assertEqual(tree.values[1].value, '-2000') 

284 

285 # test for time contained in time range, all literals 

286 tree = parser.parse("T'2020-01-01' in (T'2020-01-01', T'2020-01-02')") 

287 self.assertIsInstance(tree, exprTree.IsIn) 

288 self.assertFalse(tree.not_in) 

289 self.assertIsInstance(tree.lhs, exprTree.TimeLiteral) 

290 self.assertEqual(len(tree.values), 2) 

291 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral) 

292 self.assertIsInstance(tree.values[1], exprTree.TimeLiteral) 

293 

294 # test for time range contained in time range 

295 tree = parser.parse("(T'2020-01-01', t1) in (T'2020-01-01', t2)") 

296 self.assertIsInstance(tree, exprTree.IsIn) 

297 self.assertFalse(tree.not_in) 

298 self.assertIsInstance(tree.lhs, exprTree.TupleNode) 

299 self.assertEqual(len(tree.values), 2) 

300 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral) 

301 self.assertIsInstance(tree.values[1], exprTree.Identifier) 

302 

303 # test for point in region (we don't have region syntax yet, use 

304 # identifier) 

305 tree = parser.parse("point(1, 2) in (region1)") 

306 self.assertIsInstance(tree, exprTree.IsIn) 

307 self.assertFalse(tree.not_in) 

308 self.assertIsInstance(tree.lhs, exprTree.PointNode) 

309 self.assertEqual(len(tree.values), 1) 

310 self.assertIsInstance(tree.values[0], exprTree.Identifier) 

311 

312 # parens on right hand side are required 

313 with self.assertRaises(ParseError): 

314 parser.parse("point(1, 2) in region1") 

315 

316 # and we don't support full expressions in RHS list 

317 with self.assertRaises(ParseError): 

318 parser.parse("point(1, 2) in (x + y)") 

319 

320 def testCompareOps(self): 

321 """Tests for comparison operators 

322 """ 

323 parser = ParserYacc() 

324 

325 for op in ('=', '!=', '<', '<=', '>', '>='): 

326 tree = parser.parse('a {} 10'.format(op)) 

327 self.assertIsInstance(tree, exprTree.BinaryOp) 

328 self.assertEqual(tree.op, op) 

329 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

330 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral) 

331 self.assertEqual(tree.lhs.name, 'a') 

332 self.assertEqual(tree.rhs.value, '10') 

333 

334 def testBoolOps(self): 

335 """Tests for boolean operators 

336 """ 

337 parser = ParserYacc() 

338 

339 for op in ('OR', 'AND'): 

340 tree = parser.parse('a {} b'.format(op)) 

341 self.assertIsInstance(tree, exprTree.BinaryOp) 

342 self.assertEqual(tree.op, op) 

343 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

344 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

345 self.assertEqual(tree.lhs.name, 'a') 

346 self.assertEqual(tree.rhs.name, 'b') 

347 

348 tree = parser.parse('NOT b') 

349 self.assertIsInstance(tree, exprTree.UnaryOp) 

350 self.assertEqual(tree.op, 'NOT') 

351 self.assertIsInstance(tree.operand, exprTree.Identifier) 

352 self.assertEqual(tree.operand.name, 'b') 

353 

354 def testFunctionCall(self): 

355 """Tests for function calls 

356 """ 

357 parser = ParserYacc() 

358 

359 tree = parser.parse("f()") 

360 self.assertIsInstance(tree, exprTree.FunctionCall) 

361 self.assertEqual(tree.name, "f") 

362 self.assertEqual(tree.args, []) 

363 

364 tree = parser.parse("f1(a)") 

365 self.assertIsInstance(tree, exprTree.FunctionCall) 

366 self.assertEqual(tree.name, "f1") 

367 self.assertEqual(len(tree.args), 1) 

368 self.assertIsInstance(tree.args[0], exprTree.Identifier) 

369 self.assertEqual(tree.args[0].name, "a") 

370 

371 tree = parser.parse("anything_goes('a', x+y, ((a AND b) or (C = D)), NOT T < 42., Z IN (1,2,3,4))") 

372 self.assertIsInstance(tree, exprTree.FunctionCall) 

373 self.assertEqual(tree.name, "anything_goes") 

374 self.assertEqual(len(tree.args), 5) 

375 self.assertIsInstance(tree.args[0], exprTree.StringLiteral) 

376 self.assertIsInstance(tree.args[1], exprTree.BinaryOp) 

377 self.assertIsInstance(tree.args[2], exprTree.Parens) 

378 self.assertIsInstance(tree.args[3], exprTree.UnaryOp) 

379 self.assertIsInstance(tree.args[4], exprTree.IsIn) 

380 

381 with self.assertRaises(ParseError): 

382 parser.parse("f.ff()") 

383 

384 def testPointNode(self): 

385 """Tests for POINT() function 

386 """ 

387 parser = ParserYacc() 

388 

389 # POINT function makes special node type 

390 tree = parser.parse("POINT(Object.ra, 0.0)") 

391 self.assertIsInstance(tree, exprTree.PointNode) 

392 self.assertIsInstance(tree.ra, exprTree.Identifier) 

393 self.assertEqual(tree.ra.name, "Object.ra") 

394 self.assertIsInstance(tree.dec, exprTree.NumericLiteral) 

395 self.assertEqual(tree.dec.value, "0.0") 

396 

397 # it is not case sensitive 

398 tree = parser.parse("Point(1, 1)") 

399 self.assertIsInstance(tree, exprTree.PointNode) 

400 

401 def testTupleNode(self): 

402 """Tests for tuple 

403 """ 

404 parser = ParserYacc() 

405 

406 # test with simple identifier and literal 

407 tree = parser.parse("(Object.ra, 0.0)") 

408 self.assertIsInstance(tree, exprTree.TupleNode) 

409 self.assertEqual(len(tree.items), 2) 

410 self.assertIsInstance(tree.items[0], exprTree.Identifier) 

411 self.assertEqual(tree.items[0].name, "Object.ra") 

412 self.assertIsInstance(tree.items[1], exprTree.NumericLiteral) 

413 self.assertEqual(tree.items[1].value, "0.0") 

414 

415 # any expression can appear in tuple 

416 tree = parser.parse("(x+y, ((a AND b) or (C = D)))") 

417 self.assertIsInstance(tree, exprTree.TupleNode) 

418 self.assertEqual(len(tree.items), 2) 

419 self.assertIsInstance(tree.items[0], exprTree.BinaryOp) 

420 self.assertIsInstance(tree.items[1], exprTree.Parens) 

421 

422 # only two items can appear in a tuple 

423 with self.assertRaises(ParseError): 

424 parser.parse("(1, 2, 3)") 

425 

426 def testExpression(self): 

427 """Test for more or less complete expression""" 

428 parser = ParserYacc() 

429 

430 expression = ("((instrument='HSC' AND detector != 9) OR instrument='CFHT') " 

431 "AND tract=8766 AND patch.cell_x > 5 AND " 

432 "patch.cell_y < 4 AND band='i'") 

433 

434 tree = parser.parse(expression) 

435 self.assertIsInstance(tree, exprTree.BinaryOp) 

436 self.assertEqual(tree.op, 'AND') 

437 self.assertIsInstance(tree.lhs, exprTree.BinaryOp) 

438 # AND is left-associative, so rhs operand will be the 

439 # last sub-expressions 

440 self.assertIsInstance(tree.rhs, exprTree.BinaryOp) 

441 self.assertEqual(tree.rhs.op, '=') 

442 self.assertIsInstance(tree.rhs.lhs, exprTree.Identifier) 

443 self.assertEqual(tree.rhs.lhs.name, 'band') 

444 self.assertIsInstance(tree.rhs.rhs, exprTree.StringLiteral) 

445 self.assertEqual(tree.rhs.rhs.value, 'i') 

446 

447 def testSubstitution(self): 

448 """Test for identifier substitution""" 

449 # substitution is not recursive, so we can swap id2/id3 

450 idMap = { 

451 "id1": exprTree.StringLiteral("id1 value"), 

452 "id2": exprTree.Identifier("id3"), 

453 "id3": exprTree.Identifier("id2"), 

454 "POINT": exprTree.StringLiteral("not used"), 

455 "OR": exprTree.StringLiteral("not used"), 

456 } 

457 parser = ParserYacc(idMap=idMap) 

458 

459 expression = ("id1 = 'v'") 

460 tree = parser.parse(expression) 

461 self.assertIsInstance(tree, exprTree.BinaryOp) 

462 self.assertEqual(tree.op, '=') 

463 self.assertIsInstance(tree.lhs, exprTree.StringLiteral) 

464 self.assertEqual(tree.lhs.value, "id1 value") 

465 

466 expression = ("id2 - id3") 

467 tree = parser.parse(expression) 

468 self.assertIsInstance(tree, exprTree.BinaryOp) 

469 self.assertEqual(tree.op, '-') 

470 self.assertIsInstance(tree.lhs, exprTree.Identifier) 

471 self.assertEqual(tree.lhs.name, "id3") 

472 self.assertIsInstance(tree.rhs, exprTree.Identifier) 

473 self.assertEqual(tree.rhs.name, "id2") 

474 

475 # reserved words are not substituted 

476 expression = ("id2 OR id3") 

477 tree = parser.parse(expression) 

478 self.assertIsInstance(tree, exprTree.BinaryOp) 

479 self.assertEqual(tree.op, "OR") 

480 

481 # function names are not substituted 

482 expression = ("POINT(1, 2)") 

483 tree = parser.parse(expression) 

484 self.assertIsInstance(tree, exprTree.PointNode) 

485 

486 def testException(self): 

487 """Test for exceptional cases""" 

488 

489 def _assertExc(exc, expr, token, pos, lineno, posInLine): 

490 """Check exception attribute values""" 

491 self.assertEqual(exc.expression, expr) 

492 self.assertEqual(exc.token, token) 

493 self.assertEqual(exc.pos, pos) 

494 self.assertEqual(exc.lineno, lineno) 

495 self.assertEqual(exc.posInLine, posInLine) 

496 

497 parser = ParserYacc() 

498 

499 expression = "(1, 2, 3)" 

500 with self.assertRaises(ParseError) as catcher: 

501 parser.parse(expression) 

502 _assertExc(catcher.exception, expression, ",", 5, 1, 5) 

503 

504 expression = "\n(1\n,\n 2, 3)" 

505 with self.assertRaises(ParseError) as catcher: 

506 parser.parse(expression) 

507 _assertExc(catcher.exception, expression, ",", 8, 4, 2) 

508 

509 expression = "T'not-a-time'" 

510 with self.assertRaises(ParseError) as catcher: 

511 parser.parse(expression) 

512 _assertExc(catcher.exception, expression, "not-a-time", 0, 1, 0) 

513 

514 def testStr(self): 

515 """Test for formatting""" 

516 parser = ParserYacc() 

517 

518 tree = parser.parse("(a+b)") 

519 self.assertEqual(str(tree), '(a + b)') 

520 

521 tree = parser.parse("1 in (1,'x',3)") 

522 self.assertEqual(str(tree), "1 IN (1, 'x', 3)") 

523 

524 tree = parser.parse("a not in (1,'x',3)") 

525 self.assertEqual(str(tree), "a NOT IN (1, 'x', 3)") 

526 

527 tree = parser.parse("(A or B) And NoT (x+3 > y)") 

528 self.assertEqual(str(tree), "(A OR B) AND NOT (x + 3 > y)") 

529 

530 tree = parser.parse("A in (100, 200..300:50)") 

531 self.assertEqual(str(tree), "A IN (100, 200..300:50)") 

532 

533 def testVisit(self): 

534 """Test for visitor methods""" 

535 

536 # test should cover all visit* methods 

537 parser = ParserYacc() 

538 visitor = _Visitor() 

539 

540 tree = parser.parse("(a+b)") 

541 result = tree.visit(visitor) 

542 self.assertEqual(result, "P(B(ID(a) + ID(b)))") 

543 

544 tree = parser.parse("(A or B) and not (x + 3 > y)") 

545 result = tree.visit(visitor) 

546 self.assertEqual(result, "B(P(B(ID(A) OR ID(B))) AND U(NOT P(B(B(ID(x) + N(3)) > ID(y)))))") 

547 

548 tree = parser.parse("x in (1,2) AND y NOT IN (1.1, .25, 1e2) OR z in ('a', 'b')") 

549 result = tree.visit(visitor) 

550 self.assertEqual(result, "B(B(IN(ID(x) (N(1), N(2))) AND !IN(ID(y) (N(1.1), N(.25), N(1e2))))" 

551 " OR IN(ID(z) (S(a), S(b))))") 

552 

553 tree = parser.parse("x in (1,2,5..15) AND y NOT IN (-100..100:10)") 

554 result = tree.visit(visitor) 

555 self.assertEqual(result, "B(IN(ID(x) (N(1), N(2), R(5..15))) AND !IN(ID(y) (R(-100..100:10))))") 

556 

557 tree = parser.parse("time > T'2020-03-30'") 

558 result = tree.visit(visitor) 

559 self.assertEqual(result, "B(ID(time) > T(2020-03-30 00:00:00.000))") 

560 

561 def testParseTimeStr(self): 

562 """Test for _parseTimeString method""" 

563 

564 # few expected failures 

565 bad_times = [ 

566 "", 

567 " ", 

568 "123.456e10", # no exponents 

569 "mjd-dj/123.456", # format can only have word chars 

570 "123.456/mai-tai", # scale can only have word chars 

571 "2020-03-01 00", # iso needs minutes if hour is given 

572 "2020-03-01T", # isot needs hour:minute 

573 "2020:100:12", # yday needs minutes if hour is given 

574 "format/123456.00", # unknown format 

575 "123456.00/unscale", # unknown scale 

576 ] 

577 for bad_time in bad_times: 

578 with self.assertRaises(ValueError): 

579 _parseTimeString(bad_time) 

580 

581 # each tuple is (string, value, format, scale) 

582 tests = [ 

583 ("51544.0", 51544.0, "mjd", "tai"), 

584 ("mjd/51544.0", 51544.0, "mjd", "tai"), 

585 ("51544.0/tai", 51544.0, "mjd", "tai"), 

586 ("mjd/51544.0/tai", 51544.0, "mjd", "tai"), 

587 ("MJd/51544.0/TAi", 51544.0, "mjd", "tai"), 

588 ("jd/2451544.5", 2451544.5, "jd", "tai"), 

589 ("jd/2451544.5", 2451544.5, "jd", "tai"), 

590 ("51544.0/utc", 51544.0, "mjd", "utc"), 

591 ("unix/946684800.0", 946684800., "unix", "utc"), 

592 ("cxcsec/63072064.184", 63072064.184, "cxcsec", "tt"), 

593 ("2020-03-30", "2020-03-30 00:00:00.000", "iso", "utc"), 

594 ("2020-03-30 12:20", "2020-03-30 12:20:00.000", "iso", "utc"), 

595 ("2020-03-30 12:20:33.456789", "2020-03-30 12:20:33.457", "iso", "utc"), 

596 ("2020-03-30T12:20", "2020-03-30T12:20:00.000", "isot", "utc"), 

597 ("2020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "isot", "utc"), 

598 ("isot/2020-03-30", "2020-03-30T00:00:00.000", "isot", "utc"), 

599 ("2020-03-30/tai", "2020-03-30 00:00:00.000", "iso", "tai"), 

600 ("+02020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"), 

601 ("+02020-03-30T12:20:33", "2020-03-30T12:20:33.000", "fits", "utc"), 

602 ("+02020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "fits", "utc"), 

603 ("fits/2020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"), 

604 ("2020:123", "2020:123:00:00:00.000", "yday", "utc"), 

605 ("2020:123:12:20", "2020:123:12:20:00.000", "yday", "utc"), 

606 ("2020:123:12:20:33.456789", "2020:123:12:20:33.457", "yday", "utc"), 

607 ("yday/2020:123:12:20/tai", "2020:123:12:20:00.000", "yday", "tai"), 

608 ] 

609 for time_str, value, fmt, scale in tests: 

610 time = _parseTimeString(time_str) 

611 self.assertEqual(time.value, value) 

612 self.assertEqual(time.format, fmt) 

613 self.assertEqual(time.scale, scale) 

614 

615 

616if __name__ == "__main__": 616 ↛ 617line 616 didn't jump to line 617, because the condition on line 616 was never true

617 unittest.main()