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