Coverage for core\test_leoAtFile.py: 100%
437 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20210901172411.1: * @file ../unittests/core/test_leoAtFile.py
4#@@first
5"""Tests of leoAtFile.py"""
6import os
7import tempfile
8import textwrap
9from leo.core import leoGlobals as g
10from leo.core import leoAtFile
11from leo.core import leoBridge
12from leo.core.leoTest2 import LeoUnitTest
14#@+others
15#@+node:ekr.20210901172446.1: ** class TestAtFile(LeoUnitTest)
16class TestAtFile(LeoUnitTest):
17 """Test cases for leoAtFile.py"""
19 def setUp(self):
20 # Create a pristine instance of the AtFile class.
21 super().setUp()
22 self.at = leoAtFile.AtFile(self.c)
24 #@+others
25 #@+node:ekr.20200204095726.1: *3* TestAtFile.bridge
26 def bridge(self):
27 """Return an instance of Leo's bridge."""
28 return leoBridge.controller(gui='nullGui',
29 loadPlugins=False,
30 readSettings=False,
31 silent=True,
32 verbose=False,
33 )
34 #@+node:ekr.20210905052021.28: *3* TestAtFile.test_at_scanAllDirectives
35 def test_at_scanAllDirectives(self):
37 at, c = self.at, self.c
38 d = at.scanAllDirectives(c.p)
39 # These are the commander defaults, without any settings.
40 self.assertEqual(d.get('language'), 'python')
41 self.assertEqual(d.get('tabwidth'), -4)
42 self.assertEqual(d.get('pagewidth'), 132)
43 #@+node:ekr.20210905052021.29: *3* TestAtFile.test_at_scanAllDirectives_minimal_
44 def test_at_scanAllDirectives_minimal_(self):
46 at, c = self.at, self.c
47 d = at.scanAllDirectives(c.p)
48 d = c.atFileCommands.scanAllDirectives(c.p)
49 assert d
50 #@+node:ekr.20200204094139.1: *3* TestAtFile.test_bug_1469
51 def test_bug_1469(self):
52 # Test #1469: saves renaming an external file
53 # Create a new outline with @file node and save it
54 bridge = self.bridge()
55 with tempfile.TemporaryDirectory() as temp_dir:
56 filename = f"{temp_dir}{os.sep}test_file.leo"
57 c = bridge.openLeoFile(filename)
58 p = c.rootPosition()
59 p.h = '@file 1'
60 p.b = 'b1'
61 c.save()
62 # Rename the @file node and save
63 p1 = c.rootPosition()
64 p1.h = "@file 1_renamed"
65 c.save()
66 # Remove the original "@file 1" from the disk
67 external_filename = f"{temp_dir}{os.sep}1"
68 assert os.path.exists(external_filename)
69 os.remove(external_filename)
70 assert not os.path.exists(external_filename)
71 # Change the @file contents, save and reopen the outline
72 p1.b = "b_1_changed"
73 c.save()
74 c.close()
75 c = bridge.openLeoFile(c.fileName())
76 p1 = c.rootPosition()
77 self.assertEqual(p1.h, "@file 1_renamed")
78 #@+node:ekr.20210421035527.1: *3* TestAtFile.test_bug_1889
79 def test_bug_1889(self):
80 # Test #1889: Honor ~ in ancestor @path nodes.
81 # Create a new outline with @file node and save it
82 bridge = self.bridge()
83 with tempfile.TemporaryDirectory() as temp_dir:
84 filename = f"{temp_dir}{os.sep}test_file.leo"
85 c = bridge.openLeoFile(filename)
86 root = c.rootPosition()
87 root.h = '@path ~/sub-directory/'
88 child = root.insertAsLastChild()
89 child.h = '@file test_bug_1889.py'
90 child.b = '@language python\n# test #1889'
91 path = g.fullPath(c, child)
92 assert '~' not in path, repr(path)
93 #@+node:ekr.20210901140645.13: *3* TestAtFile.test_checkPythonSyntax
94 def test_checkPythonSyntax(self):
96 at, p = self.at, self.c.p
97 s = textwrap.dedent('''\
98 # no error
99 def spam():
100 pass
101 ''')
102 assert at.checkPythonSyntax(p, s), 'fail 1'
104 s2 = textwrap.dedent('''\
105 # syntax error
106 def spam: # missing parens.
107 pass
108 ''')
110 assert not at.checkPythonSyntax(p, s2), 'fail2'
111 #@+node:ekr.20210905052021.19: *3* TestAtFile.test_directiveKind4
112 def test_directiveKind4(self):
114 at = self.at
115 at.language = 'python' # Usually set by atFile read/write logic.
116 table = [
117 ('@=', 0, at.noDirective),
118 ('@', 0, at.atDirective),
119 ('@ ', 0, at.atDirective),
120 ('@\t', 0, at.atDirective),
121 ('@\n', 0, at.atDirective),
122 ('@all', 0, at.allDirective),
123 (' @all', 4, at.allDirective),
124 (' @all', 0, at.allDirective), # 2021/11/04
125 ("@c", 0, at.cDirective),
126 ("@code", 0, at.codeDirective),
127 ("@doc", 0, at.docDirective),
128 ('@others', 0, at.othersDirective),
129 (' @others', 4, at.othersDirective),
130 # ("@end_raw", 0, at.endRawDirective), # #2276.
131 # ("@raw", 0, at.rawDirective), # #2276.
132 ]
133 for name in g.globalDirectiveList:
134 # Note: entries in g.globalDirectiveList do not start with '@'
135 if name not in ('all', 'c', 'code', 'doc', 'end_raw', 'others', 'raw',):
136 table.append(('@' + name, 0, at.miscDirective),)
137 for s, i, expected in table:
138 result = at.directiveKind4(s, i)
139 self.assertEqual(result, expected, msg=f"i: {i}, s: {s!r}")
140 #@+node:ekr.20210905052021.20: *3* TestAtFile.test_directiveKind4_2
141 def test_directiveKind4_2(self):
143 at = self.at
144 at.language = 'python' # Usually set by atFile read/write logic.
145 table = (
146 (at.othersDirective, '@others'),
147 (at.othersDirective, '@others\n'),
148 (at.othersDirective, ' @others'),
149 (at.miscDirective, '@tabwidth -4'),
150 (at.miscDirective, '@tabwidth -4\n'),
151 (at.miscDirective, '@encoding'),
152 (at.noDirective, '@encoding.setter'),
153 (at.noDirective, '@encoding("abc")'),
154 (at.noDirective, 'encoding = "abc"'),
155 (at.noDirective, '@directive'), # A crucial new test.
156 (at.noDirective, '@raw'), # 2021/11/04.
157 )
158 for expected, s in table:
159 result = at.directiveKind4(s, 0)
160 self.assertEqual(expected, result, msg=repr(s))
161 #@+node:ekr.20211106034202.1: *3* TsetAtFile.test_findSectionName
162 def test_findSectionName(self):
163 # Test code per #2303.
164 at, p = self.at, self.c.p
165 at.initWriteIvars(p)
166 ref = g.angleBrackets(' abc ')
167 table = (
168 (True, f"{ref}\n"),
169 (True, f"{ref}"),
170 (True, f" {ref} \n"),
171 (False, f"if {ref}:\n"),
172 (False, f"{ref} # comment\n"),
173 (False, f"# {ref}\n"),
174 )
175 for valid, s in table:
176 name, n1, n2 = at.findSectionName(s, 0, p)
177 self.assertEqual(valid, bool(name), msg=repr(s))
178 #@+node:ekr.20210905052021.23: *3* TestAtFile.test_parseLeoSentinel
179 def test_parseLeoSentinel(self):
181 at = self.at
182 table = (
183 # start, end, new_df, isThin, encoding
184 # pre 4.2 formats...
185 ('#', '', False, True, 'utf-8', '#@+leo-thin-encoding=utf-8.'),
186 ('#', '', False, False, 'utf-8', '#@+leo-encoding=utf-8.'),
187 # 4.2 formats...
188 ('#', '', True, True, 'utf-8', '#@+leo-ver=4-thin-encoding=utf-8,.'),
189 ('/*', '*/', True, True, 'utf-8', r'\*@+leo-ver=5-thin-encoding=utf-8,.*/'),
190 ('#', '', True, True, 'utf-8', '#@+leo-ver=5-thin'),
191 ('#', '', True, True, 'utf-16', '#@+leo-ver=5-thin-encoding=utf-16,.'),
192 )
193 try:
194 for start, end, new_df, isThin, encoding, s in table:
195 valid, new_df2, start2, end2, isThin2 = at.parseLeoSentinel(s)
196 # g.trace('start',start,'end',repr(end),'len(s)',len(s))
197 assert valid, s
198 self.assertEqual(new_df, new_df2, msg=repr(s))
199 self.assertEqual(isThin, isThin2, msg=repr(s))
200 self.assertEqual(end, end2, msg=repr(s))
201 self.assertEqual(at.encoding, encoding, msg=repr(s))
202 finally:
203 at.encoding = 'utf-8'
204 #@+node:ekr.20211102110237.1: *3* TestAtFile.test_putBody_adjacent_at_doc_part
205 def test_putBody_adjacent_at_doc_part(self):
207 at, c = self.at, self.c
208 root = c.rootPosition()
209 root.h = '@file test.html'
210 contents = textwrap.dedent('''\
211 @doc
212 First @doc part
213 @doc
214 Second @doc part
215 ''')
216 expected = textwrap.dedent('''\
217 <!--@+doc-->
218 <!--
219 First @doc part
220 -->
221 <!--@+doc-->
222 <!--
223 Second @doc part
224 -->
225 ''')
226 root.b = contents
227 at.initWriteIvars(root)
228 at.putBody(root)
229 result = ''.join(at.outputList)
230 self.assertEqual(result, expected)
231 #@+node:ekr.20211102110833.1: *3* TestAtFile.test_putBody_at_all
232 def test_putBody_at_all(self):
234 at, c = self.at, self.c
235 root = c.rootPosition()
236 root.h = '@file test.py'
237 child = root.insertAsLastChild()
238 child.h = 'child'
239 child.b = textwrap.dedent('''\
240 def spam():
241 pass
243 @ A single-line doc part.''')
244 child.v.fileIndex = '<GNX>'
245 contents = textwrap.dedent('''\
246 ATall
247 ''').replace('AT', '@')
248 expected = textwrap.dedent('''\
249 #AT+all
250 #AT+node:<GNX>: ** child
251 def spam():
252 pass
254 @ A single-line doc part.
255 #AT-all
256 ''').replace('AT', '@')
257 root.b = contents
258 at.initWriteIvars(root)
259 at.putBody(root)
260 result = ''.join(at.outputList)
261 self.assertEqual(result, expected)
262 #@+node:ekr.20211102111413.1: *3* TestAtFile.test_putBody_at_all_after_at_doc
263 def test_putBody_at_all_after_at_doc(self):
265 at, c = self.at, self.c
266 root = c.rootPosition()
267 root.h = '@file test.py'
268 contents = textwrap.dedent('''\
269 ATdoc
270 doc line 1
271 ATall
272 ''').replace('AT', '@')
273 expected = textwrap.dedent('''\
274 #AT+doc
275 # doc line 1
276 # ATall
277 ''').replace('AT', '@')
278 root.b = contents
279 at.initWriteIvars(root)
280 at.putBody(root)
281 result = ''.join(at.outputList)
282 self.assertEqual(result, expected)
283 #@+node:ekr.20211102150707.1: *3* TestAtFile.test_putBody_at_others
284 def test_putBody_at_others(self):
286 at, c = self.at, self.c
287 root = c.rootPosition()
288 root.h = '@file test_putBody_at_others.py'
289 child = root.insertAsLastChild()
290 child.h = 'child'
291 child.b = '@others\n'
292 child.v.fileIndex = '<GNX>'
293 contents = textwrap.dedent('''\
294 ATothers
295 ''').replace('AT', '@')
296 expected = textwrap.dedent('''\
297 #AT+others
298 #AT+node:<GNX>: ** child
299 #AT+others
300 #AT-others
301 #AT-others
302 ''').replace('AT', '@')
303 root.b = contents
304 at.initWriteIvars(root)
305 at.putBody(root)
306 result = ''.join(at.outputList)
307 self.assertEqual(result, expected)
308 #@+node:ekr.20211102102024.1: *3* TestAtFile.test_putBody_unterminated_at_doc_part
309 def test_putBody_unterminated_at_doc_part(self):
311 at, c = self.at, self.c
312 root = c.rootPosition()
313 root.h = '@file test.html'
314 contents = textwrap.dedent('''\
315 @doc
316 Unterminated @doc parts (not an error)
317 ''')
318 expected = textwrap.dedent('''\
319 <!--@+doc-->
320 <!--
321 Unterminated @doc parts (not an error)
322 -->
323 ''')
324 root.b = contents
325 at.initWriteIvars(root)
326 at.putBody(root)
327 result = ''.join(at.outputList)
328 self.assertEqual(result, expected)
329 #@+node:ekr.20211104154501.1: *3* TestAtFile.test_putCodeLine
330 def test_putCodeLine(self):
332 at, p = self.at, self.c.p
333 at.initWriteIvars(p)
334 at.startSentinelComment = '#'
335 table = (
336 'Line without newline',
337 'Line with newline',
338 ' ',
339 )
340 for line in table:
341 at.putCodeLine(line, 0)
342 #@+node:ekr.20211104161927.1: *3* TestAtFile.test_putDelims
343 def test_putDelims(self):
345 at, p = self.at, self.c.p
346 at.initWriteIvars(p)
347 # Cover the missing code.
348 directive = '@delims'
349 s = ' @delims <! !>\n'
350 at.putDelims(directive, s, 0)
351 #@+node:ekr.20211104155139.1: *3* TestAtFile.test_putLeadInSentinel
352 def test_putLeadInSentinel(self):
354 at, p = self.at, self.c.p
355 at.initWriteIvars(p)
356 # Cover the special case code.
357 s = ' @others\n'
358 at.putLeadInSentinel(s, 0, 2)
359 #@+node:ekr.20211104142459.1: *3* TestAtFile.test_putLine
360 def test_putLine(self):
362 at, p = self.at, self.c.p
363 at.initWriteIvars(p)
365 class Status: # at.putBody defines the status class.
366 at_comment_seen = False
367 at_delims_seen = False
368 at_warning_given = True # Always suppress warning messages.
369 has_at_others = False
370 in_code = True
372 # For now, test only the case that hasn't been covered:
373 # kind == at.othersDirective and not status.in_code
374 status = Status()
375 status.in_code = False
376 i, kind = 0, at.othersDirective
377 s = 'A doc line\n'
378 at.putLine(i, kind, p, s, status)
381 #@+node:ekr.20211104163122.1: *3* TestAtFile.test_putRefLine
382 def test_putRefLine(self):
384 at, p = self.at, self.c.p
385 at.initWriteIvars(p)
386 # Create one section definition node.
387 name1 = g.angleBrackets('section 1')
388 child1 = p.insertAsLastChild()
389 child1.h = name1
390 child1.b = "print('test_putRefLine')\n"
391 # Create the valid section reference.
392 s = f" {name1}\n"
393 # Careful: init n2 and n2.
394 name, n1, n2 = at.findSectionName(s, 0, p)
395 self.assertTrue(name)
396 at.putRefLine(s, 0, n1, n2, name, p)
399 #@+node:ekr.20210905052021.24: *3* TestAtFile.test_remove
400 def test_remove(self):
402 at = self.at
403 exists = g.os_path_exists
405 path = g.os_path_join(g.app.testDir, 'xyzzy')
406 if exists(path):
407 os.remove(path) # pragma: no cover
409 assert not exists(path)
410 assert not at.remove(path)
412 f = open(path, 'w')
413 f.write('test')
414 f.close()
416 assert exists(path)
417 assert at.remove(path)
418 assert not exists(path)
419 #@+node:ekr.20210905052021.25: *3* TestAtFile.test_replaceFile_different_contents
420 def test_replaceFile_different_contents(self):
422 at, c = self.at, self.c
423 # Duplicate init logic...
424 at.initCommonIvars()
425 at.scanAllDirectives(c.p)
426 encoding = 'utf-8'
427 try:
428 # https://stackoverflow.com/questions/23212435
429 f = tempfile.NamedTemporaryFile(delete=False, encoding=encoding, mode='w')
430 fn = f.name
431 contents = 'test contents'
432 val = at.replaceFile(contents, encoding, fn, at.root)
433 assert val, val
434 finally:
435 f.close()
436 os.unlink(f.name)
437 #@+node:ekr.20210905052021.26: *3* TestAtFile.test_replaceFile_no_target_file
438 def test_replaceFile_no_target_file(self):
440 at, c = self.at, self.c
441 # Duplicate init logic...
442 at.initCommonIvars()
443 at.scanAllDirectives(c.p)
444 encoding = 'utf-8'
445 at.outputFileName = None # The point of this test, but I'm not sure it matters.
446 try:
447 # https://stackoverflow.com/questions/23212435
448 f = tempfile.NamedTemporaryFile(delete=False, encoding=encoding, mode='w')
449 fn = f.name
450 contents = 'test contents'
451 val = at.replaceFile(contents, encoding, fn, at.root)
452 assert val, val
453 finally:
454 f.close()
455 os.unlink(f.name)
456 #@+node:ekr.20210905052021.27: *3* TestAtFile.test_replaceFile_same_contents
457 def test_replaceFile_same_contents(self):
459 at, c = self.at, self.c
460 # Duplicate init logic...
461 at.initCommonIvars()
462 at.scanAllDirectives(c.p)
463 encoding = 'utf-8'
464 try:
465 # https://stackoverflow.com/questions/23212435
466 f = tempfile.NamedTemporaryFile(delete=False, encoding=encoding, mode='w')
467 fn = f.name
468 contents = 'test contents'
469 f.write(contents)
470 f.flush()
471 val = at.replaceFile(contents, encoding, fn, at.root)
472 assert not val, val
473 finally:
474 f.close()
475 os.unlink(f.name)
476 #@+node:ekr.20210905052021.21: *3* TestAtFile.test_setPathUa
477 def test_setPathUa(self):
479 at, p = self.at, self.c.p
480 at.setPathUa(p, 'abc')
481 d = p.v.tempAttributes
482 d2 = d.get('read-path')
483 val1 = d2.get('path')
484 val2 = at.getPathUa(p)
485 table = (
486 ('d2.get', val1),
487 ('at.getPathUa', val2),
488 )
489 for kind, val in table:
490 self.assertEqual(val, 'abc', msg=kind)
491 #@+node:ekr.20210901140645.14: *3* TestAtFile.test_tabNannyNode
492 def test_tabNannyNode(self):
494 at, p = self.at, self.c.p
495 # Test 1.
496 s = textwrap.dedent("""\
497 # no error
498 def spam():
499 pass
500 """)
501 at.tabNannyNode(p, body=s)
502 # Test 2.
503 s2 = textwrap.dedent("""\
504 # syntax error
505 def spam:
506 pass
507 a = 2
508 """)
509 try:
510 at.tabNannyNode(p, body=s2)
511 except IndentationError:
512 pass
513 #@+node:ekr.20211104154115.1: *3* TestAtFile.test_validInAtOthers
514 def test_validInAtOthers(self):
516 at, p = self.at, self.c.p
518 # Just test the last line.
519 at.sentinels = False
520 at.validInAtOthers(p)
521 #@-others
522#@+node:ekr.20211031085414.1: ** class TestFastAtRead(LeoUnitTest)
523class TestFastAtRead(LeoUnitTest):
524 """Test the FastAtRead class."""
526 def setUp(self):
527 super().setUp()
528 self.x = leoAtFile.FastAtRead(self.c, gnx2vnode={})
530 #@+others
531 #@+node:ekr.20211104162514.1: *3* TestFast.test_afterref
532 def test_afterref(self):
534 c, x = self.c, self.x
535 h = '@file /test/test_afterLastRef.py'
536 root = c.rootPosition()
537 root.h = h # To match contents.
538 #@+<< define contents >>
539 #@+node:ekr.20211106112233.1: *4* << define contents >>
540 # Be careful: no line should look like a Leo sentinel!
541 contents = textwrap.dedent(f'''\
542 #AT+leo-ver=5-thin
543 #AT+node:{root.gnx}: * {h}
544 #AT@language python
546 a = 1
547 if (
548 #AT+LB test >>
549 #AT+node:ekr.20211107051401.1: ** LB test >>
550 a == 2
551 #AT-LB test >>
552 #ATafterref
553 ):
554 a = 2
555 #AT-leo
556 ''').replace('AT', '@').replace('LB', '<<')
557 #@-<< define contents >>
558 #@+<< define expected_body >>
559 #@+node:ekr.20211106115654.1: *4* << define expected_body >>
560 expected_body = textwrap.dedent('''\
561 ATlanguage python
563 a = 1
564 if (
565 LB test >> ):
566 a = 2
567 ''').replace('AT', '@').replace('LB', '<<')
568 #@-<< define expected_body >>
569 #@+<< define expected_contents >>
570 #@+node:ekr.20211107053133.1: *4* << define expected_contents >>
571 # Be careful: no line should look like a Leo sentinel!
572 expected_contents = textwrap.dedent(f'''\
573 #AT+leo-ver=5-thin
574 #AT+node:{root.gnx}: * {h}
575 #AT@language python
577 a = 1
578 if (
579 LB test >> ):
580 a = 2
581 #AT-leo
582 ''').replace('AT', '@').replace('LB', '<<')
583 #@-<< define expected_contents >>
584 x.read_into_root(contents, path='test', root=root)
585 self.assertEqual(root.b, expected_body, msg='mismatch in body')
586 s = c.atFileCommands.atFileToString(root, sentinels=True)
587 # Leo has *never* round-tripped the contents without change!
588 self.assertEqual(s, expected_contents, msg='mismatch in contents')
590 #@+node:ekr.20211103093332.1: *3* TestFast.test_at_all
591 def test_at_all(self):
593 c, x = self.c, self.x
594 h = '@file /test/test_at_all.txt'
595 root = c.rootPosition()
596 root.h = h # To match contents.
597 #@+<< define contents >>
598 #@+node:ekr.20211103093424.1: *4* << define contents >> (test_at_all)
599 # Be careful: no line should look like a Leo sentinel!
600 contents = textwrap.dedent(f'''\
601 #AT+leo-ver=5-thin
602 #AT+node:{root.gnx}: * {h}
603 # This is Leo's final resting place for dead code.
604 # Much easier to access than a git repo.
606 #AT@language python
607 #AT@killbeautify
608 #AT+all
609 #AT+node:ekr.20211103093559.1: ** node 1
610 Section references can be undefined.
612 LB missing reference >>
613 #AT+node:ekr.20211103093633.1: ** node 2
614 # ATothers doesn't matter
616 ATothers
617 #AT-all
618 #AT@nosearch
619 #AT-leo
620 ''').replace('AT', '@').replace('LB', '<<')
621 #@-<< define contents >>
622 x.read_into_root(contents, path='test', root=root)
623 s = c.atFileCommands.atFileToString(root, sentinels=True)
624 self.assertEqual(contents, s)
625 #@+node:ekr.20211101085019.1: *3* TestFast.test_at_comment (and @first)
626 def test_at_comment(self):
628 c, x = self.c, self.x
629 h = '@file /test/test_at_comment.txt'
630 root = c.rootPosition()
631 root.h = h # To match contents.
632 #@+<< define contents >>
633 #@+node:ekr.20211101090447.1: *4* << define contents >> (test_at_comment)
634 # Be careful: no line should look like a Leo sentinel!
635 contents = textwrap.dedent(f'''\
636 !!! -*- coding: utf-8 -*-
637 !!!AT+leo-ver=5-thin
638 !!!AT+node:{root.gnx}: * {h}
639 !!!AT@first
641 """Classes to read and write @file nodes."""
643 !!!AT@comment !!!
645 !!!AT+LB test >>
646 !!!AT+node:ekr.20211101090015.2: ** LB test >>
647 print('in test section')
648 print('done')
649 !!!AT-LB test >>
651 !!!AT+others
652 !!!AT+node:ekr.20211101090015.3: ** spam
653 def spam():
654 pass
655 !!!AT+node:ekr.20211101090015.4: ** eggs
656 def eggs():
657 pass
658 !!!AT-others
660 !!!AT@language plain
661 !!!AT-leo
662 ''').replace('AT', '@').replace('LB', '<<')
663 #@-<< define contents >>
664 x.read_into_root(contents, path='test', root=root)
665 s = c.atFileCommands.atFileToString(root, sentinels=True)
666 self.assertEqual(contents, s)
667 child1 = root.firstChild()
668 child2 = child1.next()
669 child3 = child2.next()
670 table = (
671 (child1, g.angleBrackets(' test ')),
672 (child2, 'spam'),
673 (child3, 'eggs'),
674 )
675 for child, h in table:
676 self.assertEqual(child.h, h)
677 #@+node:ekr.20211101111636.1: *3* TestFast.test_at_delims
678 def test_at_delims(self):
679 c, x = self.c, self.x
680 h = '@file /test/test_at_delims.txt'
681 root = c.rootPosition()
682 root.h = h # To match contents.
683 #@+<< define contents >>
684 #@+node:ekr.20211101111652.1: *4* << define contents >> (test_at_delims)
685 # Be careful: no line should look like a Leo sentinel!
686 contents = textwrap.dedent(f'''\
687 !! -*- coding: utf-8 -*-
688 #AT+leo-ver=5-thin
689 #AT+node:{root.gnx}: * {h}
690 #AT@first
692 #ATdelims !!SPACE
694 !!AT+LB test >>
695 !!AT+node:ekr.20211101111409.2: ** LB test >>
696 print('in test section')
697 print('done')
698 !!AT-LB test >>
700 !!AT+others
701 !!AT+node:ekr.20211101111409.3: ** spam
702 def spam():
703 pass
704 !!AT+node:ekr.20211101111409.4: ** eggs
705 def eggs():
706 pass
707 !!AT-others
709 !!AT@language python
710 !!AT-leo
711 ''').replace('AT', '@').replace('LB', '<<').replace('SPACE', ' ')
712 #@-<< define contents >>
713 x.read_into_root(contents, path='test', root=root)
714 s = c.atFileCommands.atFileToString(root, sentinels=True)
715 self.assertEqual(contents, s)
716 child1 = root.firstChild()
717 child2 = child1.next()
718 child3 = child2.next()
719 table = (
720 (child1, g.angleBrackets(' test ')),
721 (child2, 'spam'),
722 (child3, 'eggs'),
723 )
724 for child, h in table:
725 self.assertEqual(child.h, h)
726 #@+node:ekr.20211103095616.1: *3* TestFast.test_at_last
727 def test_at_last(self):
729 c, x = self.c, self.x
730 h = '@file /test/test_at_last.py'
731 root = c.rootPosition()
732 root.h = h # To match contents.
733 #@+<< define contents >>
734 #@+node:ekr.20211103095959.1: *4* << define contents >> (test_at_last)
735 # Be careful: no line should look like a Leo sentinel!
736 contents = textwrap.dedent(f'''\
737 #AT+leo-ver=5-thin
738 #AT+node:{root.gnx}: * {h}
739 # Test of ATlast
740 #AT+others
741 #AT+node:ekr.20211103095810.1: ** spam
742 def spam():
743 pass
744 #AT-others
745 #AT@language python
746 #AT@last
747 #AT-leo
748 # last line
749 ''').replace('AT', '@')
750 #@-<< define contents >>
751 #@+<< define expected_body >>
752 #@+node:ekr.20211104052937.1: *4* << define expected_body >> (test_at_last)
753 expected_body = textwrap.dedent('''\
754 # Test of ATlast
755 ATothers
756 ATlanguage python
757 ATlast # last line
758 ''').replace('AT', '@')
759 #@-<< define expected_body >>
760 x.read_into_root(contents, path='test', root=root)
761 self.assertEqual(root.b, expected_body)
762 s = c.atFileCommands.atFileToString(root, sentinels=True)
763 self.assertEqual(contents, s)
764 #@+node:ekr.20211103092228.1: *3* TestFast.test_at_others
765 def test_at_others(self):
767 # In particular, we want to test indented @others.
768 c, x = self.c, self.x
769 h = '@file /test/test_at_others'
770 root = c.rootPosition()
771 root.h = h # To match contents.
772 #@+<< define contents >>
773 #@+node:ekr.20211103092228.2: *4* << define contents >> (test_at_others)
774 # Be careful: no line should look like a Leo sentinel!
775 contents = textwrap.dedent(f'''\
776 #AT+leo-ver=5-thin
777 #AT+node:{root.gnx}: * {h}
778 #AT@language python
780 class AtOthersTestClass:
781 #AT+others
782 #AT+node:ekr.20211103092443.1: ** method1
783 def method1(self):
784 pass
785 #AT-others
786 #AT-leo
787 ''').replace('AT', '@').replace('LB', '<<')
788 #@-<< define contents >>
789 x.read_into_root(contents, path='test', root=root)
790 s = c.atFileCommands.atFileToString(root, sentinels=True)
791 self.assertEqual(contents, s)
792 #@+node:ekr.20211031093209.1: *3* TestFast.test_at_section_delim
793 def test_at_section_delim(self):
795 import sys
796 if sys.version_info < (3, 9, 0):
797 self.skipTest('Requires Python 3.9') # pragma: no cover
799 c, x = self.c, self.x
800 h = '@file /test/at_section_delim.py'
801 root = c.rootPosition()
802 root.h = h # To match contents.
803 #@+<< define contents >>
804 #@+node:ekr.20211101050923.1: *4* << define contents >> (test_at_section_delim)
805 # The contents of a personal test file, slightly altered.
806 contents = textwrap.dedent(f'''\
807 # -*- coding: utf-8 -*-
808 #AT+leo-ver=5-thin
809 #AT+node:{root.gnx}: * {h}
810 #AT@first
812 """Classes to read and write @file nodes."""
814 #AT@section-delims <!< >!>
816 #AT+<!< test >!>
817 #AT+node:ekr.20211029054238.1: ** <!< test >!>
818 print('in test section')
819 print('done')
820 #AT-<!< test >!>
822 #AT+others
823 #AT+node:ekr.20211030052810.1: ** spam
824 def spam():
825 pass
826 #AT+node:ekr.20211030053502.1: ** eggs
827 def eggs():
828 pass
829 #AT-others
831 #AT@language python
832 #AT-leo
833 ''').replace('#AT', '#@')
834 #@-<< define contents >>
835 x.read_into_root(contents, path='test', root=root)
836 s = c.atFileCommands.atFileToString(root, sentinels=True)
837 self.assertEqual(contents, s)
838 child1 = root.firstChild()
839 child2 = child1.next()
840 child3 = child2.next()
841 table = (
842 (child1, '<!< test >!>'),
843 (child2, 'spam'),
844 (child3, 'eggs'),
845 )
846 for child, h in table:
847 self.assertEqual(child.h, h)
848 #@+node:ekr.20211101155930.1: *3* TestFast.test_clones
849 def test_clones(self):
851 c, x = self.c, self.x
852 h = '@file /test/test_clones.py'
853 root = c.rootPosition()
854 root.h = h # To match contents.
855 #@+<< define contents >>
856 #@+node:ekr.20211101155930.2: *4* << define contents >> (test_clones)
857 # Be careful: no line should look like a Leo sentinel!
858 contents = textwrap.dedent(f'''\
859 #AT+leo-ver=5-thin
860 #AT+node:{root.gnx}: * {h}
861 #AT@language python
863 a = 1
865 #AT+others
866 #AT+node:ekr.20211101152631.1: ** cloned node
867 a = 2
868 #AT+node:ekr.20211101153300.1: *3* child
869 a = 3
870 #AT+node:ekr.20211101152631.1: ** cloned node
871 a = 2
872 #AT+node:ekr.20211101153300.1: *3* child
873 a = 3
874 #AT-others
875 #AT-leo
876 ''').replace('AT', '@').replace('LB', '<<')
877 #@-<< define contents >>
878 x.read_into_root(contents, path='test', root=root)
879 s = c.atFileCommands.atFileToString(root, sentinels=True)
880 self.assertEqual(contents, s)
881 child1 = root.firstChild()
882 child2 = child1.next()
883 grand_child1 = child1.firstChild()
884 grand_child2 = child2.firstChild()
885 table = (
886 (child1, 'cloned node'),
887 (child2, 'cloned node'),
888 (grand_child1, 'child'),
889 (grand_child2, 'child'),
890 )
891 for child, h in table:
892 self.assertEqual(child.h, h)
893 self.assertTrue(child1.isCloned())
894 self.assertTrue(child2.isCloned())
895 self.assertEqual(child1.v, child2.v)
896 self.assertFalse(grand_child1.isCloned())
897 self.assertFalse(grand_child2.isCloned())
898 #@+node:ekr.20211103080718.1: *3* TestFast.test_cweb
899 #@@language python
901 def test_cweb(self):
903 c, x = self.c, self.x
904 h = '@file /test/test_cweb.w'
905 root = c.rootPosition()
906 root.h = h # To match contents.
907 #@+<< define contents >>
908 #@+node:ekr.20211103080718.2: *4* << define contents >> (test_cweb)
909 # pylint: disable=anomalous-backslash-in-string
910 contents = textwrap.dedent(f'''\
911 ATq@@+leo-ver=5-thin@>
912 ATq@@+node:{root.gnx}: * @{h}@>
913 ATq@@@@language cweb@>
914 ATq@@@@comment @@q@@ @@>@>
916 % This is limbo in cweb mode... It should be in BSLaTeX mode, not BSc mode.
917 % The following should not be colorized: class,if,else.
919 @* this is a _cweb_ comment. Code is written in BSc.
920 "strings" should not be colorized.
921 It should be colored in BSLaTeX mode.
922 The following are not keywords in latex mode: if, else, etc.
923 Section references are _valid_ in cweb comments!
924 ATq@@+LB section ref 1 >>@>
925 ATq@@+node:ekr.20211103082104.1: ** LB section ref 1 >>@>
926 This is section 1.
927 ATq@@-LB section ref 1 >>@>
928 @c
930 and this is C code. // It is colored in BSLaTeX mode by default.
931 /* This is a C block comment. It may also be colored in restricted BSLaTeX mode. */
933 // Section refs are valid in code too, of course.
934 ATq@@+LB section ref 2 >>@>
935 ATq@@+node:ekr.20211103083538.1: ** LB section ref 2 >>@>
936 This is section 2.
937 ATq@@-LB section ref 2 >>@>
939 BSLaTeX and BSc should not be colored.
940 if else, while, do // C keywords.
941 ATq@@-leo@>
942 ''').replace('AT', '@').replace('LB', '<<').replace('BS', '\\')
943 #@-<< define contents >>
944 x.read_into_root(contents, path='test', root=root)
945 s = c.atFileCommands.atFileToString(root, sentinels=True)
946 self.assertEqual(contents, s)
947 #@+node:ekr.20211101152817.1: *3* TestFast.test_doc_parts
948 def test_doc_parts(self):
950 c, x = self.c, self.x
951 h = '@file /test/test_directives.py'
952 root = c.rootPosition()
953 root.h = h # To match contents.
954 #@+<< define contents >>
955 #@+node:ekr.20211101152843.1: *4* << define contents >> (test_doc_parts)
956 # Be careful: no line should look like a Leo sentinel!
957 contents = textwrap.dedent(f'''\
958 #AT+leo-ver=5-thin
959 #AT+node:{root.gnx}: * {h}
960 #AT@language python
962 a = 1
964 #AT+at A doc part
965 # Line 2.
966 #AT@c
968 #AT+doc
969 # Line 2
970 #
971 # Line 3
972 #AT@c
974 #AT-leo
975 ''').replace('AT', '@').replace('LB', '<<')
976 #@-<< define contents >>
977 x.read_into_root(contents, path='test', root=root)
978 s = c.atFileCommands.atFileToString(root, sentinels=True)
979 self.assertEqual(contents, s)
980 #@+node:ekr.20211101154632.1: *3* TestFast.test_html_doc_part
981 def test_html_doc_part(self):
983 c, x = self.c, self.x
984 h = '@file /test/test_html_doc_part.py'
985 root = c.rootPosition()
986 root.h = h # To match contents.
987 #@+<< define contents >>
988 #@+node:ekr.20211101154651.1: *4* << define contents >> (test_html_doc_part)
989 # Be careful: no line should look like a Leo sentinel!
990 contents = textwrap.dedent(f'''\
991 <!--AT+leo-ver=5-thin-->
992 <!--AT+node:{root.gnx}: * {h}-->
993 <!--AT@language html-->
995 <!--AT+at-->
996 <!--
997 Line 1.
999 Line 2.
1000 -->
1001 <!--AT@c-->
1002 <!--AT-leo-->
1003 ''').replace('AT', '@').replace('LB', '<<')
1004 #@-<< define contents >>
1005 x.read_into_root(contents, path='test', root=root)
1006 s = c.atFileCommands.atFileToString(root, sentinels=True)
1007 self.assertEqual(contents, s)
1008 #@+node:ekr.20211101180354.1: *3* TestFast.test_verbatim
1009 def test_verbatim(self):
1011 c, x = self.c, self.x
1012 h = '@file /test/test_verbatim.py'
1013 root = c.rootPosition()
1014 root.h = h # To match contents.
1015 #@+<< define contents >>
1016 #@+node:ekr.20211101180404.1: *4* << define contents >> (test_verbatim)
1017 # Be careful: no line should look like a Leo sentinel!
1018 contents = textwrap.dedent(f'''\
1019 #AT+leo-ver=5-thin
1020 #AT+node:{root.gnx}: * {h}
1021 #AT@language python
1022 # Test of @verbatim
1023 print('hi')
1024 #ATverbatim
1025 #AT+node (should be protected by verbatim)
1026 #AT-leo
1027 ''').replace('AT', '@').replace('LB', '<<')
1028 #@-<< define contents >>
1029 #@+<< define expected_body >>
1030 #@+node:ekr.20211106070035.1: *4* << define expected_body >> (test_verbatim)
1031 expected_body = textwrap.dedent('''\
1032 ATlanguage python
1033 # Test of @verbatim
1034 print('hi')
1035 #AT+node (should be protected by verbatim)
1036 ''').replace('AT', '@')
1037 #@-<< define expected_body >>
1038 x.read_into_root(contents, path='test', root=root)
1039 self.assertEqual(root.b, expected_body)
1040 s = c.atFileCommands.atFileToString(root, sentinels=True)
1041 self.assertEqual(contents, s)
1042 #@-others
1043#@-others
1044#@-leo