terrain.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #
  2. # This is the 'terrain' in which the lettuce lives. By convention, this is
  3. # where global setup and teardown is defined.
  4. #
  5. # We declare some attributes of the global 'world' variables here, so the
  6. # tests can safely assume they are present.
  7. #
  8. # We also use it to provide scenario invariants, such as resetting data.
  9. #
  10. from lettuce import *
  11. import subprocess
  12. import os.path
  13. import shutil
  14. import re
  15. import time
  16. # This is a list of files that are freshly copied before each scenario
  17. # The first element is the original, the second is the target that will be
  18. # used by the tests that need them
  19. copylist = [
  20. ["configurations/example.org.config.orig", "configurations/example.org.config"]
  21. ]
  22. # class that keeps track of one running process and the files
  23. # we created for it. This needs to be moved to our framework-framework
  24. # as it is not specifically for bind10
  25. class RunningProcess:
  26. def __init__(self, step, process_name, args):
  27. # set it to none first so destructor won't error if initializer did
  28. self.process = None
  29. self.step = step
  30. self.process_name = process_name
  31. self.remove_files_on_exit = True
  32. self._create_filenames()
  33. self._start_process(args)
  34. def _start_process(self, args):
  35. stderr_write = open(self.stderr_filename, "w")
  36. stdout_write = open(self.stdout_filename, "w")
  37. self.process = subprocess.Popen(args, 1, None, subprocess.PIPE,
  38. stdout_write, stderr_write)
  39. # open them again, this time for reading
  40. self.stderr = open(self.stderr_filename, "r")
  41. self.stdout = open(self.stdout_filename, "r")
  42. def mangle_filename(self, filebase, extension):
  43. filebase = re.sub("\s+", "_", filebase)
  44. filebase = re.sub("[^a-zA-Z.\-_]", "", filebase)
  45. return filebase + "." + extension
  46. def _create_filenames(self):
  47. filebase = self.step.scenario.feature.name + "-" +\
  48. self.step.scenario.name + "-" + self.process_name
  49. self.stderr_filename = self.mangle_filename(filebase, "stderr")
  50. self.stdout_filename = self.mangle_filename(filebase, "stdout")
  51. def stop_process(self):
  52. if self.process is not None:
  53. self.process.terminate()
  54. self.process.wait()
  55. self.process = None
  56. if self.remove_files_on_exit:
  57. self._remove_files()
  58. def _remove_files(self):
  59. os.remove(self.stderr_filename)
  60. os.remove(self.stdout_filename)
  61. def _wait_for_output_str(self, filename, running_file, strings, only_new):
  62. if not only_new:
  63. full_file = open(filename, "r")
  64. for line in full_file:
  65. for string in strings:
  66. if line.find(string) != -1:
  67. full_file.close()
  68. return string
  69. while True:
  70. where = running_file.tell()
  71. line = running_file.readline()
  72. if line:
  73. for string in strings:
  74. if line.find(string) != -1:
  75. return string
  76. else:
  77. time.sleep(0.5)
  78. running_file.seek(where)
  79. def wait_for_stderr_str(self, strings, only_new = True):
  80. return self._wait_for_output_str(self.stderr_filename, self.stderr,
  81. strings, only_new)
  82. def wait_for_stdout_str(self, strings, only_new = True):
  83. return self._wait_for_output_str(self.stdout_filename, self.stdout,
  84. strings, only_new)
  85. # Container class for a number of running processes
  86. # i.e. servers like bind10, etc
  87. # one-shot programs like dig or bindctl are started and closed separately
  88. class RunningProcesses:
  89. def __init__(self):
  90. self.processes = {}
  91. def add_process(self, step, process_name, args):
  92. assert process_name not in self.processes,\
  93. "Process " + name + " already running"
  94. self.processes[process_name] = RunningProcess(step, process_name, args)
  95. def get_process(self, process_name):
  96. assert process_name in self.processes,\
  97. "Process " + name + " unknown"
  98. return self.processes[process_name]
  99. def stop_process(self, process_name):
  100. assert process_name in self.processes,\
  101. "Process " + name + " unknown"
  102. self.processes[process_name].stop_process()
  103. del self.processes[process_name]
  104. def stop_all_processes(self):
  105. for process in self.processes.values():
  106. process.stop_process()
  107. def keep_files(self):
  108. for process in self.processes.values():
  109. process.remove_files_on_exit = False
  110. def wait_for_stderr_str(self, process_name, strings, only_new = True):
  111. """Wait for any of the given strings in the given processes stderr
  112. output. If only_new is True, it will only look at the lines that are
  113. printed to stderr since the last time this method was called. If
  114. False, it will also look at the previously printed lines. This will
  115. block until one of the strings is found. TODO: we may want to put in
  116. a timeout for this... Returns the string that is found"""
  117. assert process_name in self.processes,\
  118. "Process " + process_name + " unknown"
  119. return self.processes[process_name].wait_for_stderr_str(strings,
  120. only_new)
  121. def wait_for_stdout_str(self, process_name, strings, only_new = True):
  122. """Wait for any of the given strings in the given processes stderr
  123. output. If only_new is True, it will only look at the lines that are
  124. printed to stderr since the last time this method was called. If
  125. False, it will also look at the previously printed lines. This will
  126. block until one of the strings is found. TODO: we may want to put in
  127. a timeout for this... Returns the string that is found"""
  128. assert process_name in self.processes,\
  129. "Process " + process_name + " unknown"
  130. return self.processes[process_name].wait_for_stdout_str(strings,
  131. only_new)
  132. @before.each_scenario
  133. def initialize(feature):
  134. # Keep track of running processes
  135. world.processes = RunningProcesses()
  136. # Convenience variable to access the last query result from querying.py
  137. world.last_query_result = None
  138. # Some tests can modify the settings. If the tests fail half-way, or
  139. # don't clean up, this can leave configurations or data in a bad state,
  140. # so we copy them from originals before each scenario
  141. for item in copylist:
  142. shutil.copy(item[0], item[1])
  143. @after.each_scenario
  144. def cleanup(feature):
  145. # Stop any running processes we may have had around
  146. world.processes.stop_all_processes()