runner_test.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import logging
  2. import os
  3. import pytest
  4. from pathlib import Path
  5. from unittest.mock import call
  6. import cipy
  7. import cipy.runner
  8. from matchers import HasAttributes
  9. def test_environ_will_not_clobber_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
  10. monkeypatch.setenv("EXAMPLE_FOR_TEST", "5")
  11. assert os.environ["EXAMPLE_FOR_TEST"] == "5"
  12. with pytest.raises(ValueError):
  13. with cipy.runner.environ(EXAMPLE_FOR_TEST=2):
  14. pass
  15. assert os.environ["EXAMPLE_FOR_TEST"] == "5"
  16. def test_environ_applies_overrides_in_scope(monkeypatch: pytest.MonkeyPatch) -> None:
  17. monkeypatch.setenv("EXAMPLE_FOR_TEST", "5")
  18. assert os.environ["EXAMPLE_FOR_TEST"] == "5"
  19. with cipy.runner.environ(error_on_override=False, EXAMPLE_FOR_TEST=2):
  20. assert os.environ["EXAMPLE_FOR_TEST"] == "2"
  21. assert os.environ["EXAMPLE_FOR_TEST"] == "5"
  22. def test_environ_deletes_new_env_when_done(monkeypatch: pytest.MonkeyPatch) -> None:
  23. assert "EXAMPLE_FOR_TEST" not in os.environ
  24. with cipy.runner.environ(EXAMPLE_FOR_TEST=2):
  25. assert os.environ["EXAMPLE_FOR_TEST"] == "2"
  26. assert "EXAMPLE_FOR_TEST" not in os.environ
  27. class IPCAssertAction(cipy.action.Action):
  28. @cipy.runner.ipc
  29. def run(self, context: cipy.Context) -> cipy.Status:
  30. assert Path(os.environ["CI_OUTPUT"]).is_file()
  31. assert Path(os.environ["CI_ENVIRON"]).is_file()
  32. return cipy.Status.SUCCESS
  33. class IPCInputAction(cipy.action.Action):
  34. class Inputs(cipy.Inputs):
  35. foo: int = 5
  36. bar: str = "Lorem Ipsum"
  37. inputs: Inputs = Inputs()
  38. @cipy.runner.ipc
  39. def run(self, context: cipy.Context) -> cipy.Status:
  40. assert os.environ["INPUT_foo"] == "5"
  41. assert os.environ["INPUT_bar"] == "Lorem Ipsum"
  42. return cipy.Status.SUCCESS
  43. class IPCOutputAction(cipy.action.Action):
  44. class Outputs(cipy.Outputs):
  45. foo: int
  46. bar: str
  47. outputs: Outputs = cipy.required()
  48. @cipy.runner.ipc
  49. def run(self, context: cipy.Context) -> cipy.Status:
  50. with open(os.environ["CI_OUTPUT"], "w", encoding="utf8") as file:
  51. print("foo=5", file=file)
  52. print('bar="Lorem Ipsum"', file=file)
  53. return cipy.Status.SUCCESS
  54. def test_ipc_output_files_environs_are_paths() -> None:
  55. action = IPCAssertAction(name="Test")
  56. assert action.run(cipy.Context()) is cipy.Status.SUCCESS
  57. def test_ipc_injects_inputs_as_environ() -> None:
  58. action = IPCInputAction(name="Test")
  59. assert action.run(cipy.Context()) is cipy.Status.SUCCESS
  60. def test_ipc_extracts_outputs_from_tmpfile() -> None:
  61. action = IPCOutputAction(name="Test")
  62. assert action.outputs is None
  63. assert action.run(cipy.Context()) is cipy.Status.SUCCESS
  64. assert action.outputs.foo == 5
  65. assert action.outputs.bar == "Lorem Ipsum"
  66. def test_ipc_logs_outputs(ci_logger) -> None:
  67. action = IPCOutputAction(name="Test")
  68. assert action.outputs is None
  69. assert action.run(cipy.Context()) is cipy.Status.SUCCESS
  70. ci_logger.format.assert_has_calls(
  71. [
  72. call(HasAttributes(levelno=logging.DEBUG, message="outputs:")),
  73. call(HasAttributes(levelno=logging.DEBUG, args=("foo", 5))),
  74. call(HasAttributes(levelno=logging.DEBUG, args=("bar", "Lorem Ipsum"))),
  75. ]
  76. )
  77. class NoOp(cipy.action.Action):
  78. def run(self, context: cipy.Context) -> cipy.Status:
  79. return cipy.Status.NOT_RUN
  80. def test_run_logs_when_pipe_is_false(ci_logger, mock_subprocess) -> None:
  81. mock_subprocess.stdout = """
  82. This is a sentence
  83. Lorem Ipsum
  84. """
  85. action = NoOp(name="Test")
  86. cipy.runner.run(action, [])
  87. ci_logger.format.assert_has_calls(
  88. [
  89. call(HasAttributes(levelno=logging.INFO, message="This is a sentence")),
  90. call(HasAttributes(levelno=logging.INFO, message="Lorem Ipsum")),
  91. ]
  92. )
  93. def test_run_returns_when_pipe_is_true(mock_subprocess) -> None:
  94. mock_subprocess.stdout = "Lorem Ipsum"
  95. mock_subprocess.stderr = "Ooga Booga!"
  96. action = NoOp(name="Test")
  97. _, stdout, stderr = cipy.runner.run(action, [], pipe=True)
  98. assert stdout == "Lorem Ipsum"
  99. assert stderr == "Ooga Booga!"
  100. def test_preamble_logs_input_and_output(ci_logger) -> None:
  101. class PreambleAction(cipy.Action):
  102. inputs: IPCInputAction.Inputs = IPCInputAction.Inputs(foo=1, bar="Hello")
  103. outputs: IPCOutputAction.Outputs = IPCOutputAction.Outputs(
  104. foo=5, bar="Lorem Ipsum"
  105. )
  106. @cipy.runner.preamble
  107. def run(self, context: cipy.Context) -> cipy.Status:
  108. return cipy.Status.SUCCESS
  109. action = PreambleAction(name="Test")
  110. action.run(cipy.Context())
  111. ci_logger.format.assert_has_calls(
  112. [
  113. call(HasAttributes(levelno=logging.INFO, message="with:")),
  114. call(HasAttributes(levelno=logging.INFO, args=("foo", 1))),
  115. call(HasAttributes(levelno=logging.INFO, args=("bar", "Hello"))),
  116. call(HasAttributes(levelno=logging.DEBUG, message="outputs:")),
  117. call(HasAttributes(levelno=logging.DEBUG, args=("foo", 5))),
  118. call(HasAttributes(levelno=logging.DEBUG, args=("bar", "Lorem Ipsum"))),
  119. ]
  120. )