edq-utils 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of edq-utils might be problematic. Click here for more details.

edq/util/dirent_test.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import os
2
- import sys
3
- import unittest
4
2
 
3
+ import edq.testing.unittest
5
4
  import edq.util.dirent
6
5
 
7
6
  THIS_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
@@ -16,9 +15,10 @@ This test data directory is laid out as:
16
15
  │   └── c.txt
17
16
  ├── dir_empty
18
17
  ├── file_empty
19
- ├── symlinklink_a.txt -> a.txt
20
- ├── symlinklink_dir_1 -> dir_1
21
- └── symlinklink_dir_empty -> dir_empty
18
+ ├── symlink_a.txt -> a.txt
19
+ ├── symlink_dir_1 -> dir_1
20
+ ├── symlink_dir_empty -> dir_empty
21
+ └── symlink_file_empty -> file_empty
22
22
 
23
23
  Where non-empty files are filled with their filename (without the extension).
24
24
  dir_empty will not exist in the repository (since it is an empty directory),
@@ -27,12 +27,12 @@ but it will be created by _prep_temp_dir().
27
27
 
28
28
  DIRENT_TYPE_DIR = 'dir'
29
29
  DIRENT_TYPE_FILE = 'file'
30
+ DIRENT_TYPE_BROKEN_SYMLINK = 'broken_symlink'
30
31
 
31
- class TestDirentOperations(unittest.TestCase):
32
+ class TestDirent(edq.testing.unittest.BaseTest):
32
33
  """ Test basic operations on dirents. """
33
34
 
34
- @unittest.skipIf((sys.platform.startswith("win") and (sys.version_info > (3, 11))), "windows symlink behavior")
35
- def test_dirent_base(self):
35
+ def test_setup(self):
36
36
  """ Test that the base temp directory is properly setup. """
37
37
 
38
38
  temp_dir = self._prep_temp_dir()
@@ -45,14 +45,792 @@ class TestDirentOperations(unittest.TestCase):
45
45
  (os.path.join('dir_1', 'dir_2', 'c.txt'), DIRENT_TYPE_FILE),
46
46
  ('dir_empty', DIRENT_TYPE_DIR),
47
47
  ('file_empty', DIRENT_TYPE_FILE),
48
- ('symlinklink_a.txt', DIRENT_TYPE_FILE, True),
49
- ('symlinklink_dir_1', DIRENT_TYPE_DIR, True),
50
- ('symlinklink_dir_empty', DIRENT_TYPE_DIR, True),
48
+ ('symlink_a.txt', DIRENT_TYPE_FILE, True),
49
+ ('symlink_dir_1', DIRENT_TYPE_DIR, True),
50
+ ('symlink_dir_empty', DIRENT_TYPE_DIR, True),
51
+ ('symlink_file_empty', DIRENT_TYPE_FILE, True),
51
52
  ]
52
53
 
53
54
  self._check_existing_paths(temp_dir, expected_paths)
54
55
 
55
- def test_dirent_operations_remove(self):
56
+ def test_contains_path_base(self):
57
+ """ Test checking path containment. """
58
+
59
+ temp_dir = self._prep_temp_dir()
60
+
61
+ # [(parent, child, contains?), ...]
62
+ test_cases = [
63
+ # Containment
64
+ ('a', os.path.join('a', 'b', 'c'), True),
65
+ (os.path.join('a', 'b'), os.path.join('a', 'b', 'c'), True),
66
+ ('.', os.path.join('a', 'b', 'c'), True),
67
+ ('..', '.', True),
68
+
69
+ # Self No Containment
70
+ ('a', 'a', False),
71
+ (os.path.join('a', 'b', 'c'), os.path.join('a', 'b', 'c'), False),
72
+ ('.', '.', False),
73
+
74
+ # Trivial No Containment
75
+ ('a', 'b', False),
76
+ ('z', os.path.join('a', 'b', 'c'), False),
77
+ ('aa', os.path.join('a', 'b', 'c'), False),
78
+ ('a', os.path.join('aa', 'b', 'c'), False),
79
+
80
+ # Child Contains Parent
81
+ (os.path.join('a', 'b', 'c'), 'a', False),
82
+ (os.path.join('a', 'b', 'c'), os.path.join('a', 'b'), False),
83
+ ]
84
+
85
+ for (i, test_case) in enumerate(test_cases):
86
+ (parent, child, expected) = test_case
87
+
88
+ with self.subTest(msg = f"Case {i} ('{parent}' ⊂ '{child}'):"):
89
+ parent = os.path.join(temp_dir, parent)
90
+ child = os.path.join(temp_dir, child)
91
+
92
+ actual = edq.util.dirent.contains_path(parent, child)
93
+ self.assertEqual(expected, actual)
94
+
95
+ def test_read_write_file_bytes_base(self):
96
+ """ Test reading and writing a file as bytes. """
97
+
98
+ # [(path, write kwargs, read kwargs, write contents, expected contents, error substring), ...]
99
+ # All conent should be strings that will be encoded.
100
+ test_cases = [
101
+ # Base
102
+ (
103
+ "test.txt",
104
+ {},
105
+ {},
106
+ "test",
107
+ "test",
108
+ None,
109
+ ),
110
+
111
+ # Empty Write
112
+ (
113
+ "test.txt",
114
+ {},
115
+ {},
116
+ "",
117
+ "",
118
+ None,
119
+ ),
120
+
121
+ # None Write
122
+ (
123
+ "test.txt",
124
+ {},
125
+ {},
126
+ None,
127
+ "",
128
+ None,
129
+ ),
130
+
131
+ # Clobber
132
+ (
133
+ "a.txt",
134
+ {},
135
+ {},
136
+ "test",
137
+ "test",
138
+ None,
139
+ ),
140
+ (
141
+ "dir_1",
142
+ {},
143
+ {},
144
+ "test",
145
+ "test",
146
+ None,
147
+ ),
148
+ (
149
+ "symlink_a.txt",
150
+ {},
151
+ {},
152
+ "test",
153
+ "test",
154
+ None,
155
+ ),
156
+
157
+ # No Clobber
158
+ (
159
+ "a.txt",
160
+ {'no_clobber': True},
161
+ {},
162
+ "test",
163
+ "test",
164
+ 'already exists',
165
+ ),
166
+ (
167
+ "dir_1",
168
+ {'no_clobber': True},
169
+ {},
170
+ "test",
171
+ "test",
172
+ 'already exists',
173
+ ),
174
+ (
175
+ "symlink_a.txt",
176
+ {'no_clobber': True},
177
+ {},
178
+ "test",
179
+ "test",
180
+ 'already exists',
181
+ ),
182
+ ]
183
+
184
+ for (i, test_case) in enumerate(test_cases):
185
+ (path, write_options, read_options, write_contents, expected_contents, error_substring) = test_case
186
+
187
+ with self.subTest(msg = f"Case {i} ('{path}'):"):
188
+ temp_dir = self._prep_temp_dir()
189
+ path = os.path.join(temp_dir, path)
190
+
191
+ if (write_contents is not None):
192
+ write_contents = bytes(write_contents, edq.util.dirent.DEFAULT_ENCODING)
193
+
194
+ expected_contents = bytes(expected_contents, edq.util.dirent.DEFAULT_ENCODING)
195
+
196
+ try:
197
+ edq.util.dirent.write_file_bytes(path, write_contents, **write_options)
198
+ actual_contents = edq.util.dirent.read_file_bytes(path, **read_options)
199
+ except Exception as ex:
200
+ error_string = self.format_error_string(ex)
201
+ if (error_substring is None):
202
+ self.fail(f"Unexpected error: '{error_string}'.")
203
+
204
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
205
+
206
+ continue
207
+
208
+ if (error_substring is not None):
209
+ self.fail(f"Did not get expected error: '{error_substring}'.")
210
+
211
+ self.assertEqual(expected_contents, actual_contents)
212
+
213
+ def test_read_write_file_base(self):
214
+ """ Test reading and writing a file. """
215
+
216
+ # [(path, write kwargs, read kwargs, write contents, expected contents, error substring), ...]
217
+ test_cases = [
218
+ # Base
219
+ (
220
+ "test.txt",
221
+ {},
222
+ {},
223
+ "test",
224
+ "test",
225
+ None,
226
+ ),
227
+
228
+ # Defaults
229
+ (
230
+ "test.txt",
231
+ {},
232
+ {},
233
+ " test ",
234
+ "test",
235
+ None,
236
+ ),
237
+
238
+ # No Modifications
239
+ (
240
+ "test.txt",
241
+ {'strip': False, 'newline': False},
242
+ {'strip': False},
243
+ " test ",
244
+ " test ",
245
+ None,
246
+ ),
247
+
248
+ # No Strip
249
+ (
250
+ "test.txt",
251
+ {'strip': False, 'newline': True},
252
+ {'strip': False},
253
+ " test ",
254
+ " test \n",
255
+ None,
256
+ ),
257
+
258
+ # No Read Strip
259
+ (
260
+ "test.txt",
261
+ {},
262
+ {'strip': False},
263
+ "test",
264
+ "test\n",
265
+ None,
266
+ ),
267
+
268
+ # Empty Write
269
+ (
270
+ "test.txt",
271
+ {'newline': False},
272
+ {},
273
+ "",
274
+ "",
275
+ None,
276
+ ),
277
+
278
+ # None Write
279
+ (
280
+ "test.txt",
281
+ {'newline': False},
282
+ {},
283
+ None,
284
+ "",
285
+ None,
286
+ ),
287
+
288
+ # Clobber
289
+ (
290
+ "a.txt",
291
+ {},
292
+ {},
293
+ "test",
294
+ "test",
295
+ None,
296
+ ),
297
+ (
298
+ "dir_1",
299
+ {},
300
+ {},
301
+ "test",
302
+ "test",
303
+ None,
304
+ ),
305
+ (
306
+ "symlink_a.txt",
307
+ {},
308
+ {},
309
+ "test",
310
+ "test",
311
+ None,
312
+ ),
313
+
314
+ # No Clobber
315
+ (
316
+ "a.txt",
317
+ {'no_clobber': True},
318
+ {},
319
+ "test",
320
+ "test",
321
+ 'Destination of write already exists',
322
+ ),
323
+ (
324
+ "dir_1",
325
+ {'no_clobber': True},
326
+ {},
327
+ "test",
328
+ "test",
329
+ 'Destination of write already exists',
330
+ ),
331
+ (
332
+ "symlink_a.txt",
333
+ {'no_clobber': True},
334
+ {},
335
+ "test",
336
+ "test",
337
+ 'Destination of write already exists',
338
+ ),
339
+ ]
340
+
341
+ for (i, test_case) in enumerate(test_cases):
342
+ (path, write_options, read_options, write_contents, expected_contents, error_substring) = test_case
343
+
344
+ with self.subTest(msg = f"Case {i} ('{path}'):"):
345
+ temp_dir = self._prep_temp_dir()
346
+ path = os.path.join(temp_dir, path)
347
+
348
+ try:
349
+ edq.util.dirent.write_file(path, write_contents, **write_options)
350
+ actual_contents = edq.util.dirent.read_file(path, **read_options)
351
+ except Exception as ex:
352
+ error_string = self.format_error_string(ex)
353
+ if (error_substring is None):
354
+ self.fail(f"Unexpected error: '{error_string}'.")
355
+
356
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
357
+
358
+ continue
359
+
360
+ if (error_substring is not None):
361
+ self.fail(f"Did not get expected error: '{error_substring}'.")
362
+
363
+ self.assertEqual(expected_contents, actual_contents)
364
+
365
+ def test_copy_contents_base(self):
366
+ """
367
+ Test copying the contents of a dirent.
368
+ Note that the base functionality of copy_contents() is tested by test_setup().
369
+ """
370
+
371
+ # [(source, dest, no clobber?, error substring), ...]
372
+ test_cases = [
373
+ ('a.txt', 'dir_1', False, None),
374
+ ('a.txt', 'ZZZ', False, None),
375
+ ('dir_empty', 'dir_1', False, None),
376
+
377
+ ('dir_1', 'dir_1', False, 'Source and destination of contents copy cannot be the same'),
378
+ ('dir_empty', 'symlink_dir_empty', False, 'Source and destination of contents copy cannot be the same'),
379
+
380
+ ('a.txt', 'file_empty', False, 'Destination of contents copy exists and is not a dir'),
381
+ ]
382
+
383
+ for (i, test_case) in enumerate(test_cases):
384
+ (source, dest, no_clobber, error_substring) = test_case
385
+
386
+ with self.subTest(msg = f"Case {i} ('{source}' -> '{dest}'):"):
387
+ temp_dir = self._prep_temp_dir()
388
+
389
+ source = os.path.join(temp_dir, source)
390
+ dest = os.path.join(temp_dir, dest)
391
+
392
+ try:
393
+ edq.util.dirent.copy_contents(source, dest, no_clobber = no_clobber)
394
+ except Exception as ex:
395
+ error_string = self.format_error_string(ex)
396
+ if (error_substring is None):
397
+ self.fail(f"Unexpected error: '{error_string}'.")
398
+
399
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
400
+
401
+ continue
402
+
403
+ if (error_substring is not None):
404
+ self.fail(f"Did not get expected error: '{error_substring}'.")
405
+
406
+ def test_copy_base(self):
407
+ """ Test copying dirents. """
408
+
409
+ # [(source, dest, no_clobber?, error substring), ...]
410
+ test_cases = [
411
+ # File
412
+ ('a.txt', 'test.txt', False, None),
413
+ ('a.txt', 'test.txt', True, None),
414
+ ('a.txt', os.path.join('dir_1', 'test.txt'), False, None),
415
+ ('a.txt', os.path.join('dir_1', 'test.txt'), True, None),
416
+
417
+ # File - Clobber
418
+ ('a.txt', 'file_empty', False, None),
419
+ ('a.txt', os.path.join('dir_1', 'b.txt'), False, None),
420
+ ('a.txt', 'dir_1', False, None),
421
+ ('a.txt', os.path.join('dir_1', 'dir_2'), False, None),
422
+
423
+ # File - No Clobber
424
+ ('a.txt', 'file_empty', True, 'Destination of copy already exists'),
425
+ ('a.txt', os.path.join('dir_1', 'b.txt'), True, 'Destination of copy already exists'),
426
+ ('a.txt', 'dir_1', True, 'Destination of copy already exists'),
427
+ ('a.txt', os.path.join('dir_1', 'dir_2'), True, 'Destination of copy already exists'),
428
+
429
+ # Dir
430
+ ('dir_empty', 'test', False, None),
431
+ ('dir_empty', 'test', True, None),
432
+ ('dir_empty', os.path.join('dir_1', 'test'), False, None),
433
+ ('dir_empty', os.path.join('dir_1', 'test'), True, None),
434
+
435
+ # Dir - Clobber
436
+ ('dir_empty', 'file_empty', False, None),
437
+ ('dir_empty', os.path.join('dir_1', 'b.txt'), False, None),
438
+ ('dir_empty', 'dir_1', False, None),
439
+ ('dir_empty', os.path.join('dir_1', 'dir_2'), False, None),
440
+
441
+ # Dir - No Clobber
442
+ ('dir_empty', 'file_empty', True, 'Destination of copy already exists'),
443
+ ('dir_empty', os.path.join('dir_1', 'b.txt'), True, 'Destination of copy already exists'),
444
+ ('dir_empty', 'dir_1', True, 'Destination of copy already exists'),
445
+ ('dir_empty', os.path.join('dir_1', 'dir_2'), True, 'Destination of copy already exists'),
446
+
447
+ # Link
448
+ ('symlink_a.txt', 'test.txt', False, None),
449
+ ('symlink_dir_1', 'test', False, None),
450
+ ('symlink_dir_empty', 'test', False, None),
451
+ ('symlink_file_empty', 'test.txt', False, None),
452
+
453
+ # Link - Clobber
454
+ ('symlink_a.txt', 'file_empty', False, None),
455
+ ('symlink_a.txt', 'symlink_dir_1', False, None),
456
+
457
+ # Link - No Clobber
458
+ ('symlink_a.txt', 'file_empty', True, 'Destination of copy already exists'),
459
+ ('symlink_a.txt', 'symlink_dir_1', True, 'Destination of copy already exists'),
460
+
461
+ # Clobber Parent
462
+ (os.path.join('dir_1', 'b.txt'), 'dir_1', False, 'Destination of copy cannot contain the source.'),
463
+
464
+ # Same
465
+ ('a.txt', 'a.txt', False, None),
466
+ ('symlink_a.txt', 'a.txt', False, None),
467
+ ('a.txt', 'a.txt', True, None),
468
+ ('symlink_a.txt', 'a.txt', True, None),
469
+
470
+ # Missing Source
471
+ ('ZZZ', 'test.txt', False, 'Source of copy does not exist'),
472
+ ('ZZZ', 'test.txt', True, 'Source of copy does not exist'),
473
+ ]
474
+
475
+ for (i, test_case) in enumerate(test_cases):
476
+ (source, dest, no_clobber, error_substring) = test_case
477
+
478
+ with self.subTest(msg = f"Case {i} ('{source}' -> '{dest}'):"):
479
+ temp_dir = self._prep_temp_dir()
480
+
481
+ source = os.path.join(temp_dir, source)
482
+ dest = os.path.join(temp_dir, dest)
483
+
484
+ try:
485
+ edq.util.dirent.copy(source, dest, no_clobber = no_clobber)
486
+ except Exception as ex:
487
+ error_string = self.format_error_string(ex)
488
+ if (error_substring is None):
489
+ self.fail(f"Unexpected error: '{error_string}'.")
490
+
491
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
492
+
493
+ continue
494
+
495
+ if (error_substring is not None):
496
+ self.fail(f"Did not get expected error: '{error_substring}'.")
497
+
498
+ dirent_type, is_link = self._get_dirent_type(source)
499
+
500
+ checks = [
501
+ (source, dirent_type, is_link),
502
+ ]
503
+
504
+ if (not edq.util.dirent.same(source, dest)):
505
+ checks += [
506
+ (dest, dirent_type, is_link),
507
+ ]
508
+
509
+ self._check_existing_paths(temp_dir, checks)
510
+
511
+ def test_same_base(self):
512
+ """ Test checking for two paths pointing to the same dirent. """
513
+
514
+ temp_dir = self._prep_temp_dir()
515
+
516
+ # [(path, path, same?), ...]
517
+ test_cases = [
518
+ # Same
519
+ ('a.txt', 'a.txt', True),
520
+ ('dir_1', 'dir_1', True),
521
+ (os.path.join('dir_1', 'b.txt'), os.path.join('dir_1', 'b.txt'), True),
522
+ (os.path.join('dir_1', 'b.txt'), os.path.join('dir_1', '..', 'dir_1', 'b.txt'), True),
523
+
524
+ # Not Same
525
+ ('a.txt', 'dir_1', False),
526
+ ('a.txt', os.path.join('dir_1', 'b.txt'), False),
527
+ ('a.txt', 'file_empty', False),
528
+ ('a.txt', 'dir_empty', False),
529
+
530
+ # Not Exists
531
+ ('a.txt', 'ZZZ', False),
532
+ (os.path.join('dir_1', 'b.txt'), os.path.join('dir_1', 'ZZZ'), False),
533
+ (os.path.join('dir_1', 'b.txt'), os.path.join('ZZZ', 'b.txt'), False),
534
+
535
+ # Links
536
+ ('a.txt', 'symlink_a.txt', True),
537
+ ('a.txt', 'symlink_file_empty', False),
538
+ ('dir_1', 'symlink_dir_1', True),
539
+ ('dir_1', 'symlink_dir_empty', False),
540
+ ]
541
+
542
+ for (i, test_case) in enumerate(test_cases):
543
+ (a, b, expected) = test_case
544
+
545
+ with self.subTest(msg = f"Case {i} ('{a}' vs '{b}'):"):
546
+ a = os.path.join(temp_dir, a)
547
+ b = os.path.join(temp_dir, b)
548
+
549
+ actual = edq.util.dirent.same(a, b)
550
+ self.assertEqual(expected, actual)
551
+
552
+ def test_mkdir_base(self):
553
+ """ Test creating directories. """
554
+
555
+ temp_dir = self._prep_temp_dir()
556
+
557
+ # [(path, error substring), ...]
558
+ test_cases = [
559
+ # Base
560
+ ('new_dir_1', None),
561
+ (os.path.join('dir_1', 'new_dir_2'), None),
562
+
563
+ # Missing Parents
564
+ (os.path.join('ZZZ', 'new_dir_ZZZ'), None),
565
+ (os.path.join('ZZZ', 'YYY', 'XXX', 'new_dir_XXX'), None),
566
+
567
+ # Existing Dir
568
+ ('dir_1', None),
569
+ ('dir_empty', None),
570
+ ('symlink_dir_1', None),
571
+
572
+ # Existing Non-Dir
573
+ ('a.txt', 'Target of mkdir already exists'),
574
+ ('symlink_a.txt', 'Target of mkdir already exists'),
575
+
576
+ # Existing Non-Dir Parent
577
+ (os.path.join('dir_1', 'b.txt', 'BBB'), 'Target of mkdir contains parent'),
578
+ ]
579
+
580
+ for (i, test_case) in enumerate(test_cases):
581
+ (path, error_substring) = test_case
582
+
583
+ with self.subTest(msg = f"Case {i} ('{path}'):"):
584
+ path = os.path.join(temp_dir, path)
585
+
586
+ try:
587
+ edq.util.dirent.mkdir(path)
588
+ except Exception as ex:
589
+ error_string = self.format_error_string(ex)
590
+ if (error_substring is None):
591
+ self.fail(f"Unexpected error: '{error_string}'.")
592
+
593
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
594
+
595
+ continue
596
+
597
+ if (error_substring is not None):
598
+ self.fail(f"Did not get expected error: '{error_substring}'.")
599
+
600
+ self.assertTrue(edq.util.dirent.exists(path), 'Dir does not exist post mkdir.')
601
+
602
+ def test_get_temp_path_base(self):
603
+ """ Ensure that temp paths are not the same. """
604
+
605
+ a = edq.util.dirent.get_temp_path()
606
+ b = edq.util.dirent.get_temp_path()
607
+
608
+ self.assertNotEqual(a, b)
609
+
610
+ def test_get_temp_dir_base(self):
611
+ """ Ensure that the temp dir exists. """
612
+
613
+ path = edq.util.dirent.get_temp_dir()
614
+ self.assertTrue(edq.util.dirent.exists(path))
615
+
616
+ def test_exists_base(self):
617
+ """
618
+ Test checking for existence.
619
+
620
+ ./dir_empty and ./file_empty will be removed to check for broken links.
621
+ """
622
+
623
+ temp_dir = self._prep_temp_dir()
624
+
625
+ # Remove some dirents to break links.
626
+ edq.util.dirent.remove(os.path.join(temp_dir, 'dir_empty'))
627
+ edq.util.dirent.remove(os.path.join(temp_dir, 'file_empty'))
628
+
629
+ # [(path, exists?), ...]
630
+ test_cases = [
631
+ # File
632
+ ('a.txt', True),
633
+ (os.path.join('dir_1', 'b.txt'), True),
634
+
635
+ # Dir
636
+ ('dir_1', True),
637
+ (os.path.join('dir_1', 'dir_2'), True),
638
+
639
+ # Links
640
+ ('symlink_a.txt', True),
641
+ ('symlink_dir_1', True),
642
+ ('symlink_dir_empty', True), # Broken Link
643
+ ('symlink_file_empty', True), # Broken Link
644
+
645
+ # Not Exists
646
+ ('dir_empty', False),
647
+ ('file_empty', False),
648
+ (os.path.join('dir_1', 'ZZZ'), False),
649
+ ]
650
+
651
+ for (i, test_case) in enumerate(test_cases):
652
+ (path, expected) = test_case
653
+
654
+ with self.subTest(msg = f"Case {i} ('{path}'):"):
655
+ path = os.path.join(temp_dir, path)
656
+ actual = edq.util.dirent.exists(path)
657
+ self.assertEqual(expected, actual)
658
+
659
+ def test_move_base(self):
660
+ """
661
+ Test moving dirents.
662
+
663
+ This test will create some additional dirents:
664
+ ├── dir_1
665
+ │   └── dir_2
666
+ │   ├── a.txt
667
+ │   └── dir_empty
668
+ """
669
+
670
+ # [(source, dest, no_clobber?, error substring), ...]
671
+ # The dest can be a single string, or a tuple of (operation input, expected output).
672
+ test_cases = [
673
+ # File
674
+ ('a.txt', 'test.txt', False, None),
675
+
676
+ # Move into Dir - Explicit
677
+ ('a.txt', os.path.join('dir_1', 'a.txt'), False, None),
678
+
679
+ # Move into Dir - Implicit
680
+ ('a.txt', ('dir_1', os.path.join('dir_1', 'a.txt')), False, None),
681
+
682
+ # Move out of Dir
683
+ (os.path.join('dir_1', 'b.txt'), 'b.txt', False, None),
684
+
685
+ # Missing Parents
686
+ ('a.txt', os.path.join('dir_1', 'a', 'b', 'a.txt'), False, None),
687
+
688
+ # Same File
689
+ ('a.txt', 'a.txt', False, None),
690
+
691
+ # Clobber File with File
692
+ ('a.txt', os.path.join('dir_1', 'b.txt'), False, None),
693
+
694
+ # No Clobber File with File
695
+ ('a.txt', os.path.join('dir_1', 'b.txt'), True, 'Destination of move already exists'),
696
+
697
+ # Clobber File with File - Implicit
698
+ ('a.txt', (os.path.join('dir_1', 'dir_2'), os.path.join('dir_1', 'dir_2', 'a.txt')), False, None),
699
+
700
+ # No Clobber File with File - Implicit
701
+ ('a.txt', os.path.join('dir_1', 'dir_2'), True, 'Destination of move already exists'),
702
+
703
+ # Clobber Dir with Dir
704
+ ('dir_empty', 'dir_1', False, None),
705
+
706
+ # Clobber Dir with Dir - Implicit
707
+ ('dir_empty', (os.path.join('dir_1', 'dir_2'), os.path.join('dir_1', 'dir_2', 'dir_empty')), False, None),
708
+
709
+ # No Clobber Dir with Dir - Implicit
710
+ ('dir_empty', os.path.join('dir_1', 'dir_2'), True, 'Destination of move already exists'),
711
+ ]
712
+
713
+ for (i, test_case) in enumerate(test_cases):
714
+ (source, raw_dest, no_clobber, error_substring) = test_case
715
+
716
+ with self.subTest(msg = f"Case {i} ('{source}' -> '{raw_dest}'):"):
717
+ temp_dir = self._prep_temp_dir()
718
+
719
+ # Create the additional dirents for this test.
720
+ edq.util.dirent.copy(os.path.join(temp_dir, 'a.txt'), os.path.join(temp_dir, 'dir_1', 'dir_2', 'a.txt'))
721
+ edq.util.dirent.copy(os.path.join(temp_dir, 'dir_empty'), os.path.join(temp_dir, 'dir_1', 'dir_2', 'dir_empty'))
722
+
723
+ if (isinstance(raw_dest, tuple)):
724
+ (input_dest, expected_dest) = raw_dest
725
+ else:
726
+ input_dest = raw_dest
727
+ expected_dest = raw_dest
728
+
729
+ source = os.path.join(temp_dir, source)
730
+ input_dest = os.path.join(temp_dir, input_dest)
731
+ expected_dest = os.path.join(temp_dir, expected_dest)
732
+
733
+ try:
734
+ edq.util.dirent.move(source, input_dest, no_clobber = no_clobber)
735
+ except Exception as ex:
736
+ error_string = self.format_error_string(ex)
737
+ if (error_substring is None):
738
+ self.fail(f"Unexpected error: '{error_string}'.")
739
+
740
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
741
+
742
+ continue
743
+
744
+ if (error_substring is not None):
745
+ self.fail(f"Did not get expected error: '{error_substring}'.")
746
+
747
+ self._check_existing_paths(temp_dir, [expected_dest])
748
+
749
+ if (not edq.util.dirent.same(os.path.join(temp_dir, source), os.path.join(temp_dir, expected_dest))):
750
+ self._check_nonexisting_paths(temp_dir, [source])
751
+
752
+ def test_move_rename(self):
753
+ """ Test renaming dirents (via move()). """
754
+
755
+ temp_dir = self._prep_temp_dir()
756
+
757
+ # [(source, dest, expected error), ...]
758
+ rename_relpaths = [
759
+ # Symlink - File
760
+ ('symlink_a.txt', 'rename_symlink_a.txt', None),
761
+
762
+ # Symlink - Dir
763
+ ('symlink_dir_1', 'rename_symlink_dir_1', None),
764
+
765
+ # File in Directory
766
+ (os.path.join('dir_1', 'dir_2', 'c.txt'), os.path.join('dir_1', 'dir_2', 'rename_c.txt'), None),
767
+
768
+ # File
769
+ ('a.txt', 'rename_a.txt', None),
770
+
771
+ # Empty File
772
+ ('file_empty', 'rename_file_empty', None),
773
+
774
+ # Directory
775
+ ('dir_1', 'rename_dir_1', None),
776
+
777
+ # Empty Directory
778
+ ('dir_empty', 'rename_dir_empty', None),
779
+
780
+ # Non-Existent
781
+ ('ZZZ', 'rename_ZZZ', 'Source of move does not exist'),
782
+ ]
783
+
784
+ expected_paths = [
785
+ ('rename_a.txt', DIRENT_TYPE_FILE),
786
+ ('rename_dir_1', DIRENT_TYPE_DIR),
787
+ (os.path.join('rename_dir_1', 'b.txt'), DIRENT_TYPE_FILE),
788
+ (os.path.join('rename_dir_1', 'dir_2'), DIRENT_TYPE_DIR),
789
+ (os.path.join('rename_dir_1', 'dir_2', 'rename_c.txt'), DIRENT_TYPE_FILE),
790
+ ('rename_dir_empty', DIRENT_TYPE_DIR),
791
+ ('rename_file_empty', DIRENT_TYPE_FILE),
792
+ ('rename_symlink_a.txt', DIRENT_TYPE_BROKEN_SYMLINK, True),
793
+ ('rename_symlink_dir_1', DIRENT_TYPE_BROKEN_SYMLINK, True),
794
+ ('symlink_dir_empty', DIRENT_TYPE_BROKEN_SYMLINK, True),
795
+ ('symlink_file_empty', DIRENT_TYPE_BROKEN_SYMLINK, True),
796
+ ]
797
+
798
+ unexpected_paths = [
799
+ 'symlink_a.txt',
800
+ 'symlink_dir_1',
801
+ os.path.join('dir_1', 'dir_2', 'c.txt'),
802
+ 'a.txt',
803
+ 'file_empty',
804
+ 'dir_1',
805
+ 'dir_empty',
806
+ 'ZZZ',
807
+ 'rename_ZZZ',
808
+ ]
809
+
810
+ for (i, test_case) in enumerate(rename_relpaths):
811
+ (source, dest, error_substring) = test_case
812
+
813
+ source = os.path.join(temp_dir, source)
814
+ dest = os.path.join(temp_dir, dest)
815
+
816
+ try:
817
+ edq.util.dirent.move(source, dest)
818
+ except Exception as ex:
819
+ error_string = self.format_error_string(ex)
820
+ if (error_substring is None):
821
+ self.fail(f"Case {i}: Unexpected error: '{error_string}'.")
822
+
823
+ self.assertIn(error_substring, error_string, 'Error is not as expected.')
824
+
825
+ continue
826
+
827
+ if (error_substring is not None):
828
+ self.fail(f"Case {i}: Did not get expected error: '{error_substring}'.")
829
+
830
+ self._check_nonexisting_paths(temp_dir, unexpected_paths)
831
+ self._check_existing_paths(temp_dir, expected_paths)
832
+
833
+ def test_remove_base(self):
56
834
  """ Test removing dirents. """
57
835
 
58
836
  temp_dir = self._prep_temp_dir()
@@ -60,10 +838,10 @@ class TestDirentOperations(unittest.TestCase):
60
838
  # Remove these paths in this order.
61
839
  remove_relpaths = [
62
840
  # Symlink - File
63
- 'symlinklink_a.txt',
841
+ 'symlink_a.txt',
64
842
 
65
843
  # Symlink - Dir
66
- 'symlinklink_dir_1',
844
+ 'symlink_dir_1',
67
845
 
68
846
  # File in Directory
69
847
  os.path.join('dir_1', 'dir_2', 'c.txt'),
@@ -78,17 +856,17 @@ class TestDirentOperations(unittest.TestCase):
78
856
  'dir_1',
79
857
 
80
858
  # Empty Directory
81
- 'dir_empty'
859
+ 'dir_empty',
82
860
 
83
861
  # Non-Existent
84
- 'ZZZ'
862
+ 'ZZZ',
85
863
  ]
86
864
 
87
865
  expected_paths = [
88
866
  (os.path.join('dir_1', 'dir_2'), DIRENT_TYPE_DIR),
89
867
  ('file_empty', DIRENT_TYPE_FILE),
90
868
  # Windows has some symlink issues, so we will not check for this file.
91
- # ('symlinklink_dir_empty', DIRENT_TYPE_DIR, True),
869
+ # ('symlink_dir_empty', DIRENT_TYPE_DIR, True),
92
870
  ]
93
871
 
94
872
  for relpath in remove_relpaths:
@@ -100,8 +878,10 @@ class TestDirentOperations(unittest.TestCase):
100
878
 
101
879
  def _prep_temp_dir(self):
102
880
  temp_dir = edq.util.dirent.get_temp_dir(prefix = 'edq_test_dirent_')
881
+
103
882
  edq.util.dirent.mkdir(os.path.join(temp_dir, 'dir_empty'))
104
883
  edq.util.dirent.copy_contents(TEST_BASE_DIR, temp_dir)
884
+
105
885
  return temp_dir
106
886
 
107
887
  def _check_existing_paths(self, base_dir, raw_paths):
@@ -118,7 +898,7 @@ class TestDirentOperations(unittest.TestCase):
118
898
  for raw_path in raw_paths:
119
899
  relpath = ''
120
900
  dirent_type = None
121
- is_link = None
901
+ is_link = False
122
902
 
123
903
  if (isinstance(raw_path, str)):
124
904
  relpath = raw_path
@@ -137,9 +917,14 @@ class TestDirentOperations(unittest.TestCase):
137
917
  path = os.path.join(base_dir, relpath)
138
918
 
139
919
  # Check the path exists.
140
- if (not os.path.exists(path)):
920
+ if (not edq.util.dirent.exists(path)):
141
921
  self.fail(f"Expected path does not exist: '{relpath}'.")
142
922
 
923
+ # Check the link status.
924
+ if (is_link is not None):
925
+ if (is_link != os.path.islink(path)):
926
+ self.fail(f"Expected path does not have a matching link status. Expected {is_link}, but is {not is_link}: '{relpath}'.")
927
+
143
928
  # Check the type of the dirent.
144
929
  if (dirent_type is not None):
145
930
  if (dirent_type == DIRENT_TYPE_DIR):
@@ -148,14 +933,12 @@ class TestDirentOperations(unittest.TestCase):
148
933
  elif (dirent_type == DIRENT_TYPE_FILE):
149
934
  if (not os.path.isfile(path)):
150
935
  self.fail(f"Expected path to be a file, but it is not: '{relpath}'.")
936
+ elif (dirent_type == DIRENT_TYPE_BROKEN_SYMLINK):
937
+ if ((not os.path.islink(path)) or os.path.isfile(path) or os.path.isdir(path)):
938
+ self.fail(f"Expected path to be a broken link, but it is not: '{relpath}'.")
151
939
  else:
152
940
  raise ValueError(f"Unknown dirent type '{dirent_type}' for path: '{relpath}'.")
153
941
 
154
- # Check the link status.
155
- if (is_link is not None):
156
- if (is_link != os.path.islink(path)):
157
- self.fail(f"Expected path does not have a matching link status. Expected {is_link}, but is {not is_link}: '{relpath}'.")
158
-
159
942
  def _check_nonexisting_paths(self, base_dir, raw_paths):
160
943
  """
161
944
  Ensure that specific paths do not exists, and fail the test if they do exist.
@@ -166,5 +949,20 @@ class TestDirentOperations(unittest.TestCase):
166
949
  for relpath in raw_paths:
167
950
  path = os.path.join(base_dir, relpath)
168
951
 
169
- if (os.path.exists(path)):
952
+ if (edq.util.dirent.exists(path)):
170
953
  self.fail(f"Path exists when it should not: '{relpath}'.")
954
+
955
+ def _get_dirent_type(self, path):
956
+ is_link = os.path.islink(path)
957
+ dirent_type = None
958
+
959
+ if (os.path.isdir(path)):
960
+ dirent_type = DIRENT_TYPE_DIR
961
+ elif (os.path.isfile(path)):
962
+ dirent_type = DIRENT_TYPE_FILE
963
+ elif (os.path.islink(path)):
964
+ dirent_type = DIRENT_TYPE_BROKEN_SYMLINK
965
+ else:
966
+ raise ValueError(f"Unknown dirent type: '{path}'.")
967
+
968
+ return dirent_type, is_link