envstack 0.8.0__tar.gz → 0.8.2__tar.gz
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.
- {envstack-0.8.0/lib/envstack.egg-info → envstack-0.8.2}/PKG-INFO +1 -1
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/__init__.py +1 -1
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/util.py +18 -11
- {envstack-0.8.0 → envstack-0.8.2/lib/envstack.egg-info}/PKG-INFO +1 -1
- {envstack-0.8.0 → envstack-0.8.2}/setup.py +1 -1
- {envstack-0.8.0 → envstack-0.8.2}/tests/test_cmds.py +221 -1
- {envstack-0.8.0 → envstack-0.8.2}/tests/test_env.py +183 -11
- {envstack-0.8.0 → envstack-0.8.2}/tests/test_util.py +193 -0
- {envstack-0.8.0 → envstack-0.8.2}/LICENSE +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/README.md +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/dist.json +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/cli.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/config.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/encrypt.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/env.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/exceptions.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/logger.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/node.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/path.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack/wrapper.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack.egg-info/SOURCES.txt +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack.egg-info/dependency_links.txt +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack.egg-info/entry_points.txt +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack.egg-info/not-zip-safe +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack.egg-info/requires.txt +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/lib/envstack.egg-info/top_level.txt +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/setup.cfg +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/tests/test_encrypt.py +0 -0
- {envstack-0.8.0 → envstack-0.8.2}/tests/test_node.py +0 -0
|
@@ -51,7 +51,7 @@ null = ""
|
|
|
51
51
|
|
|
52
52
|
# regular expression pattern for bash-like variable expansion
|
|
53
53
|
variable_pattern = re.compile(
|
|
54
|
-
r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([=?])(\$\{[
|
|
54
|
+
r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([=?])(\$\{[^}]*\}[\-\w/]*|[^}]*))?\}"
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
# regular expression pattern for matching windows drive letters
|
|
@@ -311,8 +311,17 @@ def evaluate_modifiers(expression: str, environ: dict = os.environ):
|
|
|
311
311
|
# substitute all matches in the expression
|
|
312
312
|
result = variable_pattern.sub(substitute_variable, expression)
|
|
313
313
|
|
|
314
|
+
# HACK: remove trailing curly braces if they exist
|
|
315
|
+
if result.endswith("}") and not result.startswith("${"):
|
|
316
|
+
result = result.rstrip("}")
|
|
317
|
+
|
|
318
|
+
# evaluate any remaining modifiers, eg. ${VAR:=${FOO:=bar}}
|
|
319
|
+
if variable_pattern.search(result):
|
|
320
|
+
result = evaluate_modifiers(result, environ)
|
|
321
|
+
|
|
314
322
|
# dedupe path-like values and resolve separators
|
|
315
|
-
|
|
323
|
+
# TODO: replace with regex pattern to detect path-like strings
|
|
324
|
+
elif ":" in result and ("/" in result or "\\" in result):
|
|
316
325
|
result = dedupe_paths(result)
|
|
317
326
|
|
|
318
327
|
# detect recursion errors
|
|
@@ -413,7 +422,7 @@ def get_stacks():
|
|
|
413
422
|
return sorted(list(stacks))
|
|
414
423
|
|
|
415
424
|
|
|
416
|
-
def findenv(var_name):
|
|
425
|
+
def findenv(var_name: str):
|
|
417
426
|
"""
|
|
418
427
|
Returns a list of paths where the given environment var is set.
|
|
419
428
|
|
|
@@ -552,7 +561,7 @@ def dump_yaml(file_path: str, data: dict, unquote: bool = True):
|
|
|
552
561
|
pass
|
|
553
562
|
|
|
554
563
|
|
|
555
|
-
def partition_platform_data(data):
|
|
564
|
+
def partition_platform_data(data: dict):
|
|
556
565
|
"""
|
|
557
566
|
Given a data dictionary with keys 'all', 'darwin', 'linux', 'windows',
|
|
558
567
|
this function finds which key-value pairs are common across all platforms,
|
|
@@ -562,13 +571,11 @@ def partition_platform_data(data):
|
|
|
562
571
|
:param data: dictionary to partition.
|
|
563
572
|
:returns: platform partitioned dictionary.
|
|
564
573
|
"""
|
|
565
|
-
|
|
566
|
-
# move data under the "all" key if it's not already there
|
|
574
|
+
# ensure all is present
|
|
567
575
|
if "all" not in data:
|
|
568
|
-
data["all"] = data.copy()
|
|
576
|
+
data["all"] = {} # data.copy()
|
|
569
577
|
|
|
570
578
|
# platforms of interest (darwin, linux, windows)
|
|
571
|
-
# platforms = [k for k in data.keys() if k not in ("all", "include")]
|
|
572
579
|
platforms = ["darwin", "linux", "windows"]
|
|
573
580
|
|
|
574
581
|
# get the union of keys from all platforms
|
|
@@ -581,10 +588,8 @@ def partition_platform_data(data):
|
|
|
581
588
|
for key in all_platform_keys:
|
|
582
589
|
if all(key in data[p] for p in platforms):
|
|
583
590
|
# get first value for comparison later
|
|
584
|
-
# first_value = data[platforms[0]][key].value
|
|
585
591
|
first_value = data[platforms[0]][key]
|
|
586
592
|
# call it common if all platforms have the same value
|
|
587
|
-
# if all(data[p][key].value == first_value for p in platforms):
|
|
588
593
|
if all(data[p][key] == first_value for p in platforms):
|
|
589
594
|
common_keys.append(key)
|
|
590
595
|
|
|
@@ -614,8 +619,10 @@ def partition_platform_data(data):
|
|
|
614
619
|
for p in platforms:
|
|
615
620
|
new_data[p] = new_platform_dicts[p]
|
|
616
621
|
|
|
617
|
-
#
|
|
622
|
+
# ensure include is present
|
|
618
623
|
if data.get("include"):
|
|
619
624
|
new_data["include"] = data["include"]
|
|
625
|
+
else:
|
|
626
|
+
new_data["include"] = []
|
|
620
627
|
|
|
621
628
|
return new_data
|
|
@@ -40,7 +40,7 @@ with open(os.path.join(here, "README.md")) as f:
|
|
|
40
40
|
|
|
41
41
|
setup(
|
|
42
42
|
name="envstack",
|
|
43
|
-
version="0.8.
|
|
43
|
+
version="0.8.2",
|
|
44
44
|
description="Stacked environment variable management system",
|
|
45
45
|
long_description=long_description,
|
|
46
46
|
long_description_content_type="text/markdown",
|
|
@@ -350,6 +350,222 @@ HELLO=goodbye
|
|
|
350
350
|
self.assertEqual(output, expected_output)
|
|
351
351
|
|
|
352
352
|
|
|
353
|
+
class TestBake(unittest.TestCase):
|
|
354
|
+
"""Tests bake command."""
|
|
355
|
+
|
|
356
|
+
def setUp(self):
|
|
357
|
+
self.filename = "baketest.env"
|
|
358
|
+
if os.path.exists(self.filename):
|
|
359
|
+
os.remove(self.filename)
|
|
360
|
+
self.envstack_bin = os.path.join(
|
|
361
|
+
os.path.dirname(__file__), "..", "bin", "envstack"
|
|
362
|
+
)
|
|
363
|
+
envpath = os.path.join(os.path.dirname(__file__), "..", "env")
|
|
364
|
+
self.root = {
|
|
365
|
+
"linux": "/mnt/pipe",
|
|
366
|
+
"win32": "X:/pipe",
|
|
367
|
+
"darwin": "/Volumes/pipe",
|
|
368
|
+
}.get(sys.platform)
|
|
369
|
+
os.environ["ENVPATH"] = envpath
|
|
370
|
+
os.environ["INTERACTIVE"] = "0"
|
|
371
|
+
# remove so we use base64 encoding by default
|
|
372
|
+
if AESGCMEncryptor.KEY_VAR_NAME in os.environ:
|
|
373
|
+
del os.environ[AESGCMEncryptor.KEY_VAR_NAME]
|
|
374
|
+
if FernetEncryptor.KEY_VAR_NAME in os.environ:
|
|
375
|
+
del os.environ[FernetEncryptor.KEY_VAR_NAME]
|
|
376
|
+
|
|
377
|
+
def tearDown(self):
|
|
378
|
+
if os.path.exists(self.filename):
|
|
379
|
+
os.remove(self.filename)
|
|
380
|
+
|
|
381
|
+
def test_default(self):
|
|
382
|
+
"""Tests baking the default stack."""
|
|
383
|
+
command = f"%s -o {self.filename}; cat {self.filename}" % self.envstack_bin
|
|
384
|
+
expected_output = """#!/usr/bin/env envstack
|
|
385
|
+
include: []
|
|
386
|
+
all: &all
|
|
387
|
+
<<: *all
|
|
388
|
+
DEPLOY_ROOT: ${ROOT}/${ENV}
|
|
389
|
+
ENV: prod
|
|
390
|
+
ENVPATH: ${DEPLOY_ROOT}/env:${ENVPATH}
|
|
391
|
+
HELLO: ${HELLO:=world}
|
|
392
|
+
LOG_LEVEL: ${LOG_LEVEL:=INFO}
|
|
393
|
+
PATH: ${DEPLOY_ROOT}/bin:${PATH}
|
|
394
|
+
PYTHONPATH: ${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
|
|
395
|
+
darwin:
|
|
396
|
+
<<: *all
|
|
397
|
+
ROOT: /Volumes/pipe
|
|
398
|
+
linux:
|
|
399
|
+
<<: *all
|
|
400
|
+
ROOT: /mnt/pipe
|
|
401
|
+
windows:
|
|
402
|
+
<<: *all
|
|
403
|
+
ROOT: X:/pipe
|
|
404
|
+
"""
|
|
405
|
+
output = subprocess.check_output(
|
|
406
|
+
command,
|
|
407
|
+
shell=True,
|
|
408
|
+
universal_newlines=True,
|
|
409
|
+
)
|
|
410
|
+
self.assertEqual(output, expected_output)
|
|
411
|
+
|
|
412
|
+
def test_dev(self):
|
|
413
|
+
"""Tests baking the dev stack."""
|
|
414
|
+
command = f"%s dev -o {self.filename}; cat {self.filename}" % self.envstack_bin
|
|
415
|
+
expected_output = """#!/usr/bin/env envstack
|
|
416
|
+
include: []
|
|
417
|
+
all: &all
|
|
418
|
+
<<: *all
|
|
419
|
+
DEPLOY_ROOT: ${ROOT}/dev
|
|
420
|
+
ENV: dev
|
|
421
|
+
ENVPATH: ${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
|
|
422
|
+
HELLO: ${HELLO:=world}
|
|
423
|
+
LOG_LEVEL: DEBUG
|
|
424
|
+
PATH: ${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
|
|
425
|
+
PYTHONPATH: ${ROOT}/dev/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
|
|
426
|
+
darwin:
|
|
427
|
+
<<: *all
|
|
428
|
+
ROOT: /Volumes/pipe
|
|
429
|
+
linux:
|
|
430
|
+
<<: *all
|
|
431
|
+
ROOT: /mnt/pipe
|
|
432
|
+
windows:
|
|
433
|
+
<<: *all
|
|
434
|
+
ROOT: X:/pipe
|
|
435
|
+
"""
|
|
436
|
+
output = subprocess.check_output(
|
|
437
|
+
command,
|
|
438
|
+
shell=True,
|
|
439
|
+
universal_newlines=True,
|
|
440
|
+
)
|
|
441
|
+
self.assertEqual(output, expected_output)
|
|
442
|
+
|
|
443
|
+
def test_thing(self):
|
|
444
|
+
"""Tests baking the thing stack with depth of 1."""
|
|
445
|
+
command = (
|
|
446
|
+
f"%s thing -o {self.filename} --depth 1; cat {self.filename}"
|
|
447
|
+
% self.envstack_bin
|
|
448
|
+
)
|
|
449
|
+
expected_output = """#!/usr/bin/env envstack
|
|
450
|
+
include: [default]
|
|
451
|
+
all: &all
|
|
452
|
+
<<: *all
|
|
453
|
+
CHAR_LIST: [a, b, c, "${HELLO}"]
|
|
454
|
+
DICT: {a: 1, b: 2, c: "${INT}"}
|
|
455
|
+
FLOAT: 1.0
|
|
456
|
+
HELLO: goodbye
|
|
457
|
+
INT: 5
|
|
458
|
+
LOG_LEVEL: ${LOG_LEVEL:=INFO}
|
|
459
|
+
NUMBER_LIST: [1, 2, 3]
|
|
460
|
+
darwin:
|
|
461
|
+
<<: *all
|
|
462
|
+
ROOT: ${HOME}/Library/Application Support/pipe
|
|
463
|
+
linux:
|
|
464
|
+
<<: *all
|
|
465
|
+
ROOT: ${HOME}/.local/pipe
|
|
466
|
+
windows:
|
|
467
|
+
<<: *all
|
|
468
|
+
ROOT: C:/ProgramData/pipe
|
|
469
|
+
"""
|
|
470
|
+
output = subprocess.check_output(
|
|
471
|
+
command,
|
|
472
|
+
shell=True,
|
|
473
|
+
universal_newlines=True,
|
|
474
|
+
)
|
|
475
|
+
self.assertEqual(output, expected_output)
|
|
476
|
+
|
|
477
|
+
def test_default_encrypted(self):
|
|
478
|
+
"""Tests baking the default stack encrypted with base64."""
|
|
479
|
+
command = (
|
|
480
|
+
f"%s --encrypt -o {self.filename}; cat {self.filename}" % self.envstack_bin
|
|
481
|
+
)
|
|
482
|
+
expected_output = """#!/usr/bin/env envstack
|
|
483
|
+
include: []
|
|
484
|
+
all: &all
|
|
485
|
+
<<: *all
|
|
486
|
+
DEPLOY_ROOT: !encrypt JHtST09UfS8ke0VOVn0=
|
|
487
|
+
ENV: !encrypt cHJvZA==
|
|
488
|
+
ENVPATH: !encrypt JHtERVBMT1lfUk9PVH0vZW52OiR7RU5WUEFUSH0=
|
|
489
|
+
HELLO: !encrypt JHtIRUxMTzo9d29ybGR9
|
|
490
|
+
LOG_LEVEL: !encrypt JHtMT0dfTEVWRUw6PUlORk99
|
|
491
|
+
PATH: !encrypt JHtERVBMT1lfUk9PVH0vYmluOiR7UEFUSH0=
|
|
492
|
+
PYTHONPATH: !encrypt JHtERVBMT1lfUk9PVH0vbGliL3B5dGhvbjoke1BZVEhPTlBBVEh9
|
|
493
|
+
darwin:
|
|
494
|
+
<<: *all
|
|
495
|
+
ROOT: !encrypt L1ZvbHVtZXMvcGlwZQ==
|
|
496
|
+
linux:
|
|
497
|
+
<<: *all
|
|
498
|
+
ROOT: !encrypt L21udC9waXBl
|
|
499
|
+
windows:
|
|
500
|
+
<<: *all
|
|
501
|
+
ROOT: !encrypt WDovcGlwZQ==
|
|
502
|
+
"""
|
|
503
|
+
output = subprocess.check_output(
|
|
504
|
+
command,
|
|
505
|
+
shell=True,
|
|
506
|
+
universal_newlines=True,
|
|
507
|
+
)
|
|
508
|
+
self.assertEqual(output, expected_output)
|
|
509
|
+
|
|
510
|
+
def test_thing_encrypted(self):
|
|
511
|
+
"""Tests baking the thing stack with depth of 1 excrypted."""
|
|
512
|
+
command = (
|
|
513
|
+
f"%s thing --encrypt -o {self.filename} --depth 1; cat {self.filename}"
|
|
514
|
+
% self.envstack_bin
|
|
515
|
+
)
|
|
516
|
+
expected_output = """#!/usr/bin/env envstack
|
|
517
|
+
include: [default]
|
|
518
|
+
all: &all
|
|
519
|
+
<<: *all
|
|
520
|
+
CHAR_LIST: !encrypt WydhJywgJ2InLCAnYycsICcke0hFTExPfSdd
|
|
521
|
+
DICT: !encrypt eydhJzogMSwgJ2InOiAyLCAnYyc6ICcke0lOVH0nfQ==
|
|
522
|
+
FLOAT: !encrypt MS4w
|
|
523
|
+
HELLO: !encrypt Z29vZGJ5ZQ==
|
|
524
|
+
INT: !encrypt NQ==
|
|
525
|
+
LOG_LEVEL: !encrypt JHtMT0dfTEVWRUw6PUlORk99
|
|
526
|
+
NUMBER_LIST: !encrypt WzEsIDIsIDNd
|
|
527
|
+
darwin:
|
|
528
|
+
<<: *all
|
|
529
|
+
ROOT: !encrypt JHtIT01FfS9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvcGlwZQ==
|
|
530
|
+
linux:
|
|
531
|
+
<<: *all
|
|
532
|
+
ROOT: !encrypt JHtIT01FfS8ubG9jYWwvcGlwZQ==
|
|
533
|
+
windows:
|
|
534
|
+
<<: *all
|
|
535
|
+
ROOT: !encrypt QzovUHJvZ3JhbURhdGEvcGlwZQ==
|
|
536
|
+
"""
|
|
537
|
+
output = subprocess.check_output(
|
|
538
|
+
command,
|
|
539
|
+
shell=True,
|
|
540
|
+
universal_newlines=True,
|
|
541
|
+
)
|
|
542
|
+
self.assertEqual(output, expected_output)
|
|
543
|
+
|
|
544
|
+
def test_blank(self):
|
|
545
|
+
"""Tests baking a blank stack."""
|
|
546
|
+
command = (
|
|
547
|
+
f"%s doesnotexist -o {self.filename}; cat {self.filename}"
|
|
548
|
+
% self.envstack_bin
|
|
549
|
+
)
|
|
550
|
+
expected_output = """#!/usr/bin/env envstack
|
|
551
|
+
include: []
|
|
552
|
+
all: &all
|
|
553
|
+
<<: *all
|
|
554
|
+
darwin:
|
|
555
|
+
<<: *all
|
|
556
|
+
linux:
|
|
557
|
+
<<: *all
|
|
558
|
+
windows:
|
|
559
|
+
<<: *all
|
|
560
|
+
"""
|
|
561
|
+
output = subprocess.check_output(
|
|
562
|
+
command,
|
|
563
|
+
shell=True,
|
|
564
|
+
universal_newlines=True,
|
|
565
|
+
)
|
|
566
|
+
self.assertEqual(output, expected_output)
|
|
567
|
+
|
|
568
|
+
|
|
353
569
|
class TestCommands(unittest.TestCase):
|
|
354
570
|
"""Tests various envstack commands."""
|
|
355
571
|
|
|
@@ -583,4 +799,8 @@ class TestIssues(unittest.TestCase):
|
|
|
583
799
|
|
|
584
800
|
|
|
585
801
|
if __name__ == "__main__":
|
|
586
|
-
|
|
802
|
+
# TODO: make tests_cmds.py cross-platform
|
|
803
|
+
if sys.platform == "linux":
|
|
804
|
+
unittest.main()
|
|
805
|
+
else:
|
|
806
|
+
print(f"platform not supported: {sys.platform}")
|
|
@@ -214,7 +214,7 @@ class TestInit(unittest.TestCase):
|
|
|
214
214
|
self.assertEqual(os.getenv("ROOT"), self.root)
|
|
215
215
|
self.assertEqual(os.getenv("DEPLOY_ROOT"), f"{self.root}/prod")
|
|
216
216
|
self.assertTrue(len(sys.path) > len(sys_path))
|
|
217
|
-
self.assertTrue(len(os.getenv("PATH")) > len(path))
|
|
217
|
+
# self.assertTrue(len(os.getenv("PATH")) > len(path))
|
|
218
218
|
self.assertTrue("prod/lib/python" in os.getenv("PYTHONPATH"))
|
|
219
219
|
self.assertTrue("prod/bin" in os.getenv("PATH"))
|
|
220
220
|
|
|
@@ -277,6 +277,142 @@ class TestInit(unittest.TestCase):
|
|
|
277
277
|
self.assertEqual(diffs["unchanged"], original_env)
|
|
278
278
|
|
|
279
279
|
|
|
280
|
+
class TestResolveEnviron(unittest.TestCase):
|
|
281
|
+
"""Tests the resolve_environ function."""
|
|
282
|
+
|
|
283
|
+
def test_simple(self):
|
|
284
|
+
"""Tests resolving a simple environment."""
|
|
285
|
+
from envstack.env import resolve_environ
|
|
286
|
+
|
|
287
|
+
env = {"FOO": "foo", "BAR": "${FOO}"}
|
|
288
|
+
resolved = resolve_environ(env)
|
|
289
|
+
self.assertEqual(resolved["BAR"], "foo")
|
|
290
|
+
|
|
291
|
+
def test_nested(self):
|
|
292
|
+
"""Tests resolving a nested environment."""
|
|
293
|
+
from envstack.env import resolve_environ
|
|
294
|
+
|
|
295
|
+
env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": "${BAR}"}
|
|
296
|
+
resolved = resolve_environ(env)
|
|
297
|
+
self.assertEqual(resolved["BAZ"], "foo")
|
|
298
|
+
|
|
299
|
+
def test_recursive(self):
|
|
300
|
+
"""Tests resolving a recursive environment."""
|
|
301
|
+
from envstack.env import resolve_environ
|
|
302
|
+
|
|
303
|
+
env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": "${BAR}", "QUX": "${BAZ}"}
|
|
304
|
+
resolved = resolve_environ(env)
|
|
305
|
+
self.assertEqual(resolved["QUX"], "foo")
|
|
306
|
+
|
|
307
|
+
def test_recursive_list(self):
|
|
308
|
+
"""Tests resolving a recursive list environment."""
|
|
309
|
+
from envstack.env import resolve_environ
|
|
310
|
+
|
|
311
|
+
env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": ["${BAR}"]}
|
|
312
|
+
resolved = resolve_environ(env)
|
|
313
|
+
self.assertEqual(resolved["BAZ"], ["foo"])
|
|
314
|
+
|
|
315
|
+
def test_recursive_dict(self):
|
|
316
|
+
"""Tests resolving a recursive dict environment."""
|
|
317
|
+
from envstack.env import resolve_environ
|
|
318
|
+
|
|
319
|
+
env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": {"qux": "${BAR}"}}
|
|
320
|
+
resolved = resolve_environ(env)
|
|
321
|
+
self.assertEqual(resolved["BAZ"], {"qux": "foo"})
|
|
322
|
+
|
|
323
|
+
def test_expansion_modifier(self):
|
|
324
|
+
"""Tests var with an expansion modifier."""
|
|
325
|
+
from envstack.env import resolve_environ
|
|
326
|
+
|
|
327
|
+
env = {"VAR": "${VAR:=/foo/bar}"}
|
|
328
|
+
resolved = resolve_environ(env)
|
|
329
|
+
self.assertEqual(resolved["VAR"], "/foo/bar")
|
|
330
|
+
|
|
331
|
+
def test_expansion_modifier_nested_undefined(self):
|
|
332
|
+
"""Tests expansion modifier with no value."""
|
|
333
|
+
from envstack.env import resolve_environ
|
|
334
|
+
|
|
335
|
+
env = {"VAR": "${VAR:=${FOO}}"}
|
|
336
|
+
resolved = resolve_environ(env)
|
|
337
|
+
self.assertEqual(resolved["VAR"], "")
|
|
338
|
+
|
|
339
|
+
def test_expansion_modifier_nested_default(self):
|
|
340
|
+
"""Tests expansion modifier with default value."""
|
|
341
|
+
from envstack.env import resolve_environ
|
|
342
|
+
|
|
343
|
+
env = {"VAR": "${VAR:=${FOO:=bar}}"}
|
|
344
|
+
resolved = resolve_environ(env)
|
|
345
|
+
self.assertEqual(resolved["VAR"], "bar")
|
|
346
|
+
|
|
347
|
+
def test_expansion_modifier_nested_default_slash(self):
|
|
348
|
+
"""Tests expansion modifier with special chars."""
|
|
349
|
+
from envstack.env import resolve_environ
|
|
350
|
+
|
|
351
|
+
env = {"VAR": "${VAR:=${FOO:=/foo/bar}}"}
|
|
352
|
+
resolved = resolve_environ(env)
|
|
353
|
+
self.assertEqual(resolved["VAR"], "/foo/bar")
|
|
354
|
+
|
|
355
|
+
def test_expansion_modifier_deferred(self):
|
|
356
|
+
"""Tests expansion modifier with deferred value."""
|
|
357
|
+
from envstack.env import resolve_environ
|
|
358
|
+
|
|
359
|
+
env = {"VAR": "${VAR:=${FOO}}", "FOO": "${FOO:=/foo/bar}"}
|
|
360
|
+
env["BAR"] = "${BAZ}"
|
|
361
|
+
resolved = resolve_environ(env)
|
|
362
|
+
self.assertEqual(resolved["VAR"], "/foo/bar")
|
|
363
|
+
self.assertEqual(resolved["FOO"], "/foo/bar")
|
|
364
|
+
|
|
365
|
+
def test_expansion_modifier_deferred_default(self):
|
|
366
|
+
"""Tests expansion modifier with multiple deferred values."""
|
|
367
|
+
from envstack.env import resolve_environ
|
|
368
|
+
|
|
369
|
+
env = {"VAR": "${VAR:=${FOO}}", "FOO": "${FOO:=${BAR}}"}
|
|
370
|
+
env["BAR"] = "${BAZ:=/baz/qux}" # insert a default value
|
|
371
|
+
resolved = resolve_environ(env)
|
|
372
|
+
self.assertEqual(resolved["VAR"], "/baz/qux")
|
|
373
|
+
self.assertEqual(resolved["FOO"], "/baz/qux")
|
|
374
|
+
self.assertEqual(resolved["BAR"], "/baz/qux")
|
|
375
|
+
|
|
376
|
+
def test_expansion_modifier_deferred_default_slash(self):
|
|
377
|
+
"""Tests expansion modifier with multiple deferred values."""
|
|
378
|
+
from envstack.env import resolve_environ
|
|
379
|
+
|
|
380
|
+
env = {"VAR": "${VAR:=${FOO}}", "FOO": "${FOO:=${BAR}}"}
|
|
381
|
+
env["BAR"] = "${BAZ:=/baz/qux}"
|
|
382
|
+
env["BAZ"] = "${QUX}"
|
|
383
|
+
resolved = resolve_environ(env)
|
|
384
|
+
self.assertEqual(resolved["VAR"], "/baz/qux")
|
|
385
|
+
self.assertEqual(resolved["FOO"], "/baz/qux")
|
|
386
|
+
self.assertEqual(resolved["BAR"], "/baz/qux")
|
|
387
|
+
self.assertEqual(resolved["BAZ"], "")
|
|
388
|
+
self.assertRaises(KeyError, lambda: resolved["QUX"])
|
|
389
|
+
|
|
390
|
+
# FIXME: these tests are not passing
|
|
391
|
+
# def test_recursive_list_dict(self):
|
|
392
|
+
# """Tests resolving a recursive list dict environment."""
|
|
393
|
+
# from envstack.env import resolve_environ
|
|
394
|
+
|
|
395
|
+
# env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": [{"qux": "${BAR}"}]}
|
|
396
|
+
# resolved = resolve_environ(env)
|
|
397
|
+
# self.assertEqual(resolved["BAZ"], [{"qux": "foo"}])
|
|
398
|
+
|
|
399
|
+
# def test_recursive_dict_list(self):
|
|
400
|
+
# """Tests resolving a recursive dict list environment."""
|
|
401
|
+
# from envstack.env import resolve_environ
|
|
402
|
+
|
|
403
|
+
# env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": {"qux": ["${BAR}"]}}
|
|
404
|
+
# resolved = resolve_environ(env)
|
|
405
|
+
# self.assertEqual(resolved["BAZ"], {"qux": ["foo"]})
|
|
406
|
+
|
|
407
|
+
# def test_recursive_list_list(self):
|
|
408
|
+
# """Tests resolving a recursive list list environment."""
|
|
409
|
+
# from envstack.env import resolve_environ
|
|
410
|
+
|
|
411
|
+
# env = {"FOO": "foo", "BAR": "${FOO}", "BAZ": [["${BAR}"]]}
|
|
412
|
+
# resolved = resolve_environ(env)
|
|
413
|
+
# self.assertEqual(resolved["BAZ"], [["foo"]])
|
|
414
|
+
|
|
415
|
+
|
|
280
416
|
class TestBakeEnviron(unittest.TestCase):
|
|
281
417
|
def setUp(self):
|
|
282
418
|
self.root = create_test_root()
|
|
@@ -292,16 +428,29 @@ class TestBakeEnviron(unittest.TestCase):
|
|
|
292
428
|
"""Bakes a given stack and compares values."""
|
|
293
429
|
from envstack.env import bake_environ, load_environ
|
|
294
430
|
|
|
295
|
-
|
|
431
|
+
env = load_environ(stack_name)
|
|
296
432
|
envstack.revert() # FIXME: revert should not be required
|
|
297
433
|
baked = bake_environ(stack_name)
|
|
298
434
|
|
|
299
435
|
# make sure environment sources are different
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
436
|
+
if stack_name == "doesnotexist":
|
|
437
|
+
self.assertEqual(env.sources, [])
|
|
438
|
+
self.assertEqual(baked.sources, [])
|
|
439
|
+
elif stack_name == "default":
|
|
440
|
+
self.assertTrue(len(env.sources[0].includes()) == 0)
|
|
441
|
+
else:
|
|
442
|
+
self.assertNotEqual(env.sources, baked.sources)
|
|
443
|
+
self.assertTrue(len(env.sources) > 0)
|
|
444
|
+
self.assertEqual(baked.sources, [])
|
|
445
|
+
self.assertTrue(len(env) > 0)
|
|
446
|
+
self.assertTrue(len(baked) > 0)
|
|
447
|
+
|
|
448
|
+
# "include" key should not be present
|
|
449
|
+
self.assertTrue("include" not in env)
|
|
450
|
+
self.assertTrue("include" not in baked)
|
|
451
|
+
|
|
452
|
+
# compare the values
|
|
453
|
+
for key, value in env.items():
|
|
305
454
|
if key == "STACK": # skip the stack name
|
|
306
455
|
continue
|
|
307
456
|
self.assertEqual(baked[key], value)
|
|
@@ -314,11 +463,22 @@ class TestBakeEnviron(unittest.TestCase):
|
|
|
314
463
|
|
|
315
464
|
envstack.revert() # FIXME: revert should not be required
|
|
316
465
|
baked2_reloaded = load_environ("baked")
|
|
317
|
-
self.assertNotEqual(baked2.sources, baked2_reloaded.sources)
|
|
318
|
-
self.assertTrue(len(baked2) > 0)
|
|
319
|
-
self.assertTrue(len(baked2_reloaded) > 0)
|
|
320
466
|
|
|
321
|
-
|
|
467
|
+
# make sure environment sources are different
|
|
468
|
+
if stack_name == "doesnotexist":
|
|
469
|
+
self.assertEqual(env.sources, [])
|
|
470
|
+
self.assertEqual(baked2.sources, [])
|
|
471
|
+
else:
|
|
472
|
+
self.assertNotEqual(baked2.sources, baked2_reloaded.sources)
|
|
473
|
+
self.assertTrue(len(baked2) > 0)
|
|
474
|
+
self.assertTrue(len(baked2_reloaded) > 0)
|
|
475
|
+
|
|
476
|
+
# "include" key should not be present
|
|
477
|
+
self.assertTrue("include" not in env)
|
|
478
|
+
self.assertTrue("include" not in baked2)
|
|
479
|
+
|
|
480
|
+
# compare the values
|
|
481
|
+
for key, value in env.items():
|
|
322
482
|
if key == "STACK":
|
|
323
483
|
continue
|
|
324
484
|
self.assertEqual(baked2_reloaded[key], value)
|
|
@@ -330,6 +490,10 @@ class TestBakeEnviron(unittest.TestCase):
|
|
|
330
490
|
"""Tests baking the default environment."""
|
|
331
491
|
self.bake_environ("default")
|
|
332
492
|
|
|
493
|
+
def test_bake_empty(self):
|
|
494
|
+
"""Tests baking new environment."""
|
|
495
|
+
self.bake_environ("doesnotexist")
|
|
496
|
+
|
|
333
497
|
def test_bake_dev(self):
|
|
334
498
|
"""Tests baking the dev environment."""
|
|
335
499
|
self.bake_environ("dev")
|
|
@@ -372,6 +536,10 @@ class TestEncryptEnviron(unittest.TestCase):
|
|
|
372
536
|
self.assertTrue(len(env) > 0)
|
|
373
537
|
self.assertTrue(len(encrypted) > 0)
|
|
374
538
|
|
|
539
|
+
# "include" key should not be present
|
|
540
|
+
self.assertTrue("include" not in env)
|
|
541
|
+
self.assertTrue("include" not in encrypted)
|
|
542
|
+
|
|
375
543
|
for key, value in env.items():
|
|
376
544
|
if key == "STACK": # skip the stack name
|
|
377
545
|
continue
|
|
@@ -417,6 +585,10 @@ class TestEncryptEnviron(unittest.TestCase):
|
|
|
417
585
|
self.assertTrue(len(default) > 0)
|
|
418
586
|
self.assertTrue(len(encrypted) > 0)
|
|
419
587
|
|
|
588
|
+
# "include" key should not be present
|
|
589
|
+
self.assertTrue("include" not in default)
|
|
590
|
+
self.assertTrue("include" not in encrypted)
|
|
591
|
+
|
|
420
592
|
for key, value in default.items():
|
|
421
593
|
if key == "STACK": # skip the stack name
|
|
422
594
|
continue
|
|
@@ -39,6 +39,7 @@ import unittest
|
|
|
39
39
|
from envstack import config
|
|
40
40
|
from envstack.exceptions import CyclicalReference
|
|
41
41
|
from envstack.util import (
|
|
42
|
+
null,
|
|
42
43
|
encode,
|
|
43
44
|
evaluate_modifiers,
|
|
44
45
|
get_stack_name,
|
|
@@ -48,60 +49,199 @@ from envstack.util import (
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class TestEvaluateModifiers(unittest.TestCase):
|
|
52
|
+
"""Tests for evaluate_modifiers function."""
|
|
53
|
+
|
|
51
54
|
def test_no_substitution(self):
|
|
55
|
+
"""Test no substitution."""
|
|
52
56
|
expression = "world"
|
|
53
57
|
result = evaluate_modifiers(expression)
|
|
54
58
|
self.assertEqual(result, "world")
|
|
55
59
|
|
|
56
60
|
def test_direct_substitution(self):
|
|
61
|
+
"""Test direct substitution."""
|
|
57
62
|
expression = "${VAR}"
|
|
58
63
|
environ = {"VAR": "hello"}
|
|
59
64
|
result = evaluate_modifiers(expression, environ)
|
|
60
65
|
self.assertEqual(result, "hello")
|
|
61
66
|
|
|
67
|
+
def test_default_null_value(self):
|
|
68
|
+
"""Test var null value."""
|
|
69
|
+
expression = "${VAR}"
|
|
70
|
+
environ = {}
|
|
71
|
+
result = evaluate_modifiers(expression, environ)
|
|
72
|
+
self.assertEqual(result, null)
|
|
73
|
+
|
|
62
74
|
def test_default_value(self):
|
|
75
|
+
"""Test default value."""
|
|
63
76
|
expression = "${VAR:=default}"
|
|
64
77
|
environ = {"VAR": "hello"}
|
|
65
78
|
result = evaluate_modifiers(expression, environ)
|
|
66
79
|
self.assertEqual(result, "hello")
|
|
67
80
|
|
|
68
81
|
def test_default_value_empty_env(self):
|
|
82
|
+
"""Test default value with empty environment."""
|
|
69
83
|
expression = "${VAR:=default}"
|
|
70
84
|
environ = {}
|
|
71
85
|
result = evaluate_modifiers(expression, environ)
|
|
72
86
|
self.assertEqual(result, "default")
|
|
73
87
|
|
|
88
|
+
def test_pathlike_value(self):
|
|
89
|
+
"""Test path-like value with two vars."""
|
|
90
|
+
expression = "${ROOT}/${ENV}"
|
|
91
|
+
environ = {"ROOT": "/usr/local", "ENV": "env"}
|
|
92
|
+
result = evaluate_modifiers(expression, environ)
|
|
93
|
+
self.assertEqual(result, "/usr/local/env")
|
|
94
|
+
|
|
95
|
+
def test_pathlike_value_colon_separated(self):
|
|
96
|
+
"""Test colon-separated path-like value with two vars."""
|
|
97
|
+
expression = "${DEPLOY_ROOT}/env:${ENVPATH}"
|
|
98
|
+
environ = {"DEPLOY_ROOT": "/usr/local/lib", "ENVPATH": "/mnt/env"}
|
|
99
|
+
result = evaluate_modifiers(expression, environ)
|
|
100
|
+
self.assertEqual(result, f"/usr/local/lib/env{os.pathsep}/mnt/env")
|
|
101
|
+
|
|
74
102
|
def test_default_value_with_default_args(self):
|
|
103
|
+
"""Test default value with default args."""
|
|
75
104
|
expression = "${HELLO:=world}"
|
|
76
105
|
result = evaluate_modifiers(expression)
|
|
77
106
|
self.assertEqual(result, os.getenv("HELLO", "world"))
|
|
78
107
|
|
|
79
108
|
def test_error_message(self):
|
|
109
|
+
"""Test error message."""
|
|
80
110
|
expression = "${VAR:?error message}"
|
|
81
111
|
environ = {"VAR": "hello"}
|
|
82
112
|
result = evaluate_modifiers(expression, environ)
|
|
83
113
|
self.assertEqual(result, "hello")
|
|
84
114
|
|
|
85
115
|
def test_error_message_raise(self):
|
|
116
|
+
"""Test error message raise."""
|
|
86
117
|
expression = "${VAR:?error message}"
|
|
87
118
|
environ = {}
|
|
88
119
|
with self.assertRaises(ValueError):
|
|
89
120
|
evaluate_modifiers(expression, environ)
|
|
90
121
|
|
|
91
122
|
def test_cyclical_reference_error(self):
|
|
123
|
+
"""Test cyclical reference error."""
|
|
92
124
|
expression = "${VAR}"
|
|
93
125
|
environ = {"VAR": "${FOO}", "FOO": "${BAR}", "BAR": "${VAR}"}
|
|
94
126
|
with self.assertRaises(CyclicalReference):
|
|
95
127
|
evaluate_modifiers(expression, environ)
|
|
96
128
|
|
|
97
129
|
def test_multiple_substitutions(self):
|
|
130
|
+
"""Test multiple substitutions."""
|
|
98
131
|
expression = "${VAR}/${FOO:=foobar}/${BAR:?error message}"
|
|
99
132
|
environ = {"VAR": "hello", "BAR": "world"}
|
|
100
133
|
result = evaluate_modifiers(expression, environ)
|
|
101
134
|
self.assertEqual(result, "hello/foobar/world")
|
|
102
135
|
|
|
136
|
+
def test_embedded_substitution(self):
|
|
137
|
+
"""Test embedded substitution with default."""
|
|
138
|
+
expression = "${VAR:=${FOO:=bar}}"
|
|
139
|
+
environ = {"FOO": "foo"}
|
|
140
|
+
result = evaluate_modifiers(expression, environ)
|
|
141
|
+
self.assertEqual(result, "foo")
|
|
142
|
+
|
|
143
|
+
def test_embedded_substitution_default(self):
|
|
144
|
+
"""Test embedded substitution with default."""
|
|
145
|
+
expression = "${VAR:=${FOO:=bar}}"
|
|
146
|
+
environ = {}
|
|
147
|
+
result = evaluate_modifiers(expression, environ)
|
|
148
|
+
self.assertEqual(result, "bar")
|
|
149
|
+
|
|
150
|
+
def test_embedded_substitution_value_var(self):
|
|
151
|
+
"""Test embedded substitution with value for VAR."""
|
|
152
|
+
expression = "${VAR:=${FOO}}"
|
|
153
|
+
environ = {"VAR": "foobar"}
|
|
154
|
+
result = evaluate_modifiers(expression, environ)
|
|
155
|
+
self.assertEqual(result, "foobar")
|
|
156
|
+
|
|
157
|
+
def test_embedded_substitution_value_var_foo(self):
|
|
158
|
+
"""Test embedded substitution with values for VAR and FOO."""
|
|
159
|
+
expression = "${VAR:=${FOO}}"
|
|
160
|
+
environ = {"VAR": "foobar", "FOO": "barfoo"}
|
|
161
|
+
result = evaluate_modifiers(expression, environ)
|
|
162
|
+
self.assertEqual(result, "foobar")
|
|
163
|
+
|
|
164
|
+
def test_embedded_substitution_value_foo(self):
|
|
165
|
+
"""Test embedded substitution with value for FOO."""
|
|
166
|
+
expression = "${VAR:=${FOO}}"
|
|
167
|
+
environ = {"FOO": "barfoo"}
|
|
168
|
+
result = evaluate_modifiers(expression, environ)
|
|
169
|
+
self.assertEqual(result, "barfoo")
|
|
170
|
+
|
|
171
|
+
def test_embedded_substitution_value_var_slashes(self):
|
|
172
|
+
"""Test embedded substitution with value with special chars."""
|
|
173
|
+
expression = "${VAR:=${FOO}}"
|
|
174
|
+
environ = {"VAR": "/foo/bar"}
|
|
175
|
+
result = evaluate_modifiers(expression, environ)
|
|
176
|
+
self.assertEqual(result, "/foo/bar")
|
|
177
|
+
|
|
178
|
+
def test_embedded_substitution_value_bar_slashes(self):
|
|
179
|
+
"""Test embedded substitution with value with special chars."""
|
|
180
|
+
expression = "${VAR:=${FOO}}"
|
|
181
|
+
environ = {"FOO": "/bar/foo"}
|
|
182
|
+
result = evaluate_modifiers(expression, environ)
|
|
183
|
+
self.assertEqual(result, "/bar/foo")
|
|
184
|
+
|
|
185
|
+
def test_embedded_substitution_multiple_one(self):
|
|
186
|
+
"""Test multiple embedded substitution."""
|
|
187
|
+
expression = "${VAR:=${FOO:=${BAR}}}"
|
|
188
|
+
environ = {"BAR": "/foo/bar"}
|
|
189
|
+
result = evaluate_modifiers(expression, environ)
|
|
190
|
+
self.assertEqual(result, "/foo/bar")
|
|
191
|
+
|
|
192
|
+
def test_embedded_substitution_multiple_two(self):
|
|
193
|
+
"""Test multiple embedded substitution with value."""
|
|
194
|
+
expression = "${VAR:=${FOO:=${BAR}}}"
|
|
195
|
+
environ = {"FOO": "/test/a/b/c"}
|
|
196
|
+
result = evaluate_modifiers(expression, environ)
|
|
197
|
+
self.assertEqual(result, "/test/a/b/c")
|
|
198
|
+
|
|
199
|
+
def test_embedded_substitution_multiple_three(self):
|
|
200
|
+
"""Test multiple embedded substitution with value."""
|
|
201
|
+
expression = "${VAR:=${FOO:=${BAR}}}"
|
|
202
|
+
environ = {"VAR": "/test/x/y/z"}
|
|
203
|
+
result = evaluate_modifiers(expression, environ)
|
|
204
|
+
self.assertEqual(result, "/test/x/y/z")
|
|
205
|
+
|
|
206
|
+
def test_embedded_substitution_multiple_default(self):
|
|
207
|
+
"""Test multiple embedded substitution with default value."""
|
|
208
|
+
expression = "${VAR:=${FOO:=${BAR:=/foo/bar/baz}}}"
|
|
209
|
+
environ = {}
|
|
210
|
+
result = evaluate_modifiers(expression, environ)
|
|
211
|
+
self.assertEqual(result, "/foo/bar/baz")
|
|
212
|
+
|
|
213
|
+
def test_embedded_substitution_prefix(self):
|
|
214
|
+
"""Test embedded substitution with prefix."""
|
|
215
|
+
expression = "${VAR:=default}/path"
|
|
216
|
+
environ = {"VAR": "value"}
|
|
217
|
+
result = evaluate_modifiers(expression, environ)
|
|
218
|
+
self.assertEqual(result, "value/path")
|
|
219
|
+
|
|
220
|
+
def test_embedded_substitution_prefix_default(self):
|
|
221
|
+
"""Test embedded substitution with prefix with default."""
|
|
222
|
+
expression = "${VAR:=default}/path"
|
|
223
|
+
environ = {}
|
|
224
|
+
result = evaluate_modifiers(expression, environ)
|
|
225
|
+
self.assertEqual(result, "default/path")
|
|
226
|
+
|
|
227
|
+
def test_embedded_substitution_with_slash(self):
|
|
228
|
+
"""Test embedded substitution with special char /."""
|
|
229
|
+
expression = "${VAR:=${FOO}/bar}}"
|
|
230
|
+
environ = {"FOO": "foo"}
|
|
231
|
+
result = evaluate_modifiers(expression, environ)
|
|
232
|
+
self.assertEqual(result, "foo/bar")
|
|
233
|
+
|
|
234
|
+
def test_embedded_substitution_with_hyphen(self):
|
|
235
|
+
"""Test embedded substitution with special char -."""
|
|
236
|
+
expression = "${VAR:=${FOO}-bar}}"
|
|
237
|
+
environ = {"FOO": "foo"}
|
|
238
|
+
result = evaluate_modifiers(expression, environ)
|
|
239
|
+
self.assertEqual(result, "foo-bar")
|
|
240
|
+
|
|
103
241
|
|
|
104
242
|
class TestUtils(unittest.TestCase):
|
|
243
|
+
"""Tests for util.py module."""
|
|
244
|
+
|
|
105
245
|
def test_encode(self):
|
|
106
246
|
env = {
|
|
107
247
|
"VAR1": "value1",
|
|
@@ -173,6 +313,8 @@ darwin:
|
|
|
173
313
|
|
|
174
314
|
|
|
175
315
|
class TestDedupePaths(unittest.TestCase):
|
|
316
|
+
"""Tests for dedupe_paths function."""
|
|
317
|
+
|
|
176
318
|
def test_dedupe_list(self):
|
|
177
319
|
"""Test dedupe_list function."""
|
|
178
320
|
from envstack.util import dedupe_list
|
|
@@ -269,6 +411,8 @@ class TestDedupePaths(unittest.TestCase):
|
|
|
269
411
|
|
|
270
412
|
|
|
271
413
|
class TestSafeEval(unittest.TestCase):
|
|
414
|
+
"""Tests for safe_eval function."""
|
|
415
|
+
|
|
272
416
|
def test_safe_eval_string(self):
|
|
273
417
|
value = "hello"
|
|
274
418
|
result = safe_eval(value)
|
|
@@ -301,8 +445,54 @@ class TestSafeEval(unittest.TestCase):
|
|
|
301
445
|
|
|
302
446
|
|
|
303
447
|
class TestPartitionPlatformData(unittest.TestCase):
|
|
448
|
+
"""Tests for partition_platform_data function."""
|
|
449
|
+
|
|
450
|
+
def test_partition_platform_data_empty(self):
|
|
451
|
+
"""Test partition_platform_data with empty data."""
|
|
452
|
+
data = {}
|
|
453
|
+
result = partition_platform_data(data)
|
|
454
|
+
expected_result = {
|
|
455
|
+
"include": [],
|
|
456
|
+
"all": {
|
|
457
|
+
"<<": "*all",
|
|
458
|
+
},
|
|
459
|
+
"darwin": {
|
|
460
|
+
"<<": "*all",
|
|
461
|
+
},
|
|
462
|
+
"linux": {
|
|
463
|
+
"<<": "*all",
|
|
464
|
+
},
|
|
465
|
+
"windows": {
|
|
466
|
+
"<<": "*all",
|
|
467
|
+
},
|
|
468
|
+
}
|
|
469
|
+
self.assertEqual(result, expected_result)
|
|
470
|
+
|
|
471
|
+
def test_partition_platform_data_empty_includes(self):
|
|
472
|
+
"""Test partition_platform_data with empty includes."""
|
|
473
|
+
data = {"include": []}
|
|
474
|
+
result = partition_platform_data(data)
|
|
475
|
+
expected_result = {
|
|
476
|
+
"include": [],
|
|
477
|
+
"all": {
|
|
478
|
+
"<<": "*all",
|
|
479
|
+
},
|
|
480
|
+
"darwin": {
|
|
481
|
+
"<<": "*all",
|
|
482
|
+
},
|
|
483
|
+
"linux": {
|
|
484
|
+
"<<": "*all",
|
|
485
|
+
},
|
|
486
|
+
"windows": {
|
|
487
|
+
"<<": "*all",
|
|
488
|
+
},
|
|
489
|
+
}
|
|
490
|
+
self.assertEqual(result, expected_result)
|
|
491
|
+
|
|
304
492
|
def test_partition_platform_data(self):
|
|
493
|
+
"""Test partition_platform_data with data."""
|
|
305
494
|
data = {
|
|
495
|
+
"include": [],
|
|
306
496
|
"all": {
|
|
307
497
|
"key1": "value1",
|
|
308
498
|
"key2": "value2",
|
|
@@ -329,6 +519,7 @@ class TestPartitionPlatformData(unittest.TestCase):
|
|
|
329
519
|
}
|
|
330
520
|
|
|
331
521
|
expected_result = {
|
|
522
|
+
"include": [],
|
|
332
523
|
"all": {
|
|
333
524
|
"<<": "*all",
|
|
334
525
|
"key1": "value1",
|
|
@@ -357,6 +548,8 @@ class TestPartitionPlatformData(unittest.TestCase):
|
|
|
357
548
|
|
|
358
549
|
|
|
359
550
|
class TestIssue18(unittest.TestCase):
|
|
551
|
+
"""Tests for issue #18."""
|
|
552
|
+
|
|
360
553
|
def test_non_cyclical_reference_error_1(self):
|
|
361
554
|
expression = "${FOO}"
|
|
362
555
|
environ = {"FOO": "${FOO}"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|