Browse Source

[1290] initial version of file-based stdout/stderr checking

and a bit more of a framework to keep track of running processes
(still in the works)
partial support for keeping output files, but no method of triggering that
(we may want to use a specific world.error or something)
Jelte Jansen 13 years ago
parent
commit
673ef8efd5

+ 8 - 48
tests/lettuce/features/bind10_control.py

@@ -1,46 +1,12 @@
 from lettuce import *
 import subprocess
+import re
 
 def check_lines(output, lines):
     for line in lines:
         if output.find(line) != -1:
             return line
 
-@world.absorb
-def wait_for_output_lines_stdout(process_name, lines, examine_past = True):
-    assert process_name in world.processes
-    if examine_past:
-        for output in world.processes_stdout[process_name]:
-            for line in lines:
-                if output.find(line) != -1:
-                    return line
-    found = False
-    while not found:
-        output = world.processes[process_name].stdout.readline()
-        # store any line, for examine_skipped
-        world.processes_stdout[process_name].append(output)
-        for line in lines:
-            if output.find(line) != -1:
-                return line
-
-@world.absorb
-def wait_for_output_lines_stderr(process_name, lines, examine_past = True):
-    assert process_name in world.processes,\
-        "No process named '" + process_name + "' known"
-    if examine_past:
-        for output in world.processes_stderr[process_name]:
-            for line in lines:
-                if output.find(line) != -1:
-                    return line
-    found = False
-    while not found:
-        output = world.processes[process_name].stderr.readline()
-        # store any line, for examine_skipped
-        world.processes_stderr[process_name].append(output)
-        for line in lines:
-            if output.find(line) != -1:
-                return line
-
 @step('start bind10(?: with configuration (\S+))?' +\
       '(?: with cmdctl port (\d+))?(?: as (\S+))?')
 def start_bind10(step, config_file, cmdctl_port, process_name):
@@ -60,26 +26,20 @@ def start_bind10(step, config_file, cmdctl_port, process_name):
         args.append('-m')
         args.append(process_name + '_msgq.socket')
 
-    assert process_name not in world.processes,\
-        "There already seems to be a process named " + process_name
-    world.processes[process_name] = subprocess.Popen(args, 1, None,
-                                                     subprocess.PIPE,
-                                                     subprocess.PIPE,
-                                                     subprocess.PIPE)
-    world.processes_stdout[process_name] = []
-    world.processes_stderr[process_name] = []
+    world.processes.add_process(step, process_name, args)
+
     # check output to know when startup has been completed
     # TODO what to do on failure?
-    message = world.wait_for_output_lines_stderr(process_name,
-                                                 ["BIND10_STARTUP_COMPLETE",
-                                                  "BIND10_STARTUP_ERROR"])
-    assert message == "BIND10_STARTUP_COMPLETE", "Got: " + message
+    message = world.processes.wait_for_stderr_str(process_name,
+                                                  ["BIND10_STARTUP_COMPLETE",
+                                                   "BIND10_STARTUP_ERROR"])
+    assert message == "BIND10_STARTUP_COMPLETE", "Got: " + str(message)
 
 @step('wait for bind10 auth (?:of (\w+) )?to start')
 def wait_for_auth(step, process_name):
     if process_name is None:
         process_name = "bind10"
-    world.wait_for_output_lines_stderr(process_name, ['AUTH_SERVER_STARTED'])
+    world.processes.wait_for_stderr_str(process_name, ['AUTH_SERVER_STARTED'])
 
 @step('have bind10 running(?: with configuration ([\w.]+))?')
 def have_bind10_running(step, config_file):

+ 2 - 1
tests/lettuce/features/querying.py

@@ -118,7 +118,8 @@ def query(step, query_name, qtype, qclass, addr, port, rcode):
     if port is None:
         port = 47806
     query_result = QueryResult(query_name, qtype, qclass, addr, port)
-    assert query_result.rcode == rcode, "Got " + query_result.rcode
+    assert query_result.rcode == rcode,\
+        "Expected: " + rcode + ", got " + query_result.rcode
     world.last_query_result = query_result
 
 @step('The SOA serial for ([\w.]+) should be ([0-9]+)')

+ 3 - 3
tests/lettuce/features/steps.py

@@ -8,15 +8,15 @@ import os
 
 @step('stop process (\w+)')
 def stop_a_named_process(step, process_name):
-    world.stop_process(process_name)
+    world.processes.stop_process(process_name)
 
 @step('wait for (new )?(\w+) stderr message (\w+)')
 def wait_for_message(step, new, process_name, message):
-    world.wait_for_output_lines_stderr(process_name, [message], new is None)
+    world.processes.wait_for_stderr_str(process_name, [message], new)
 
 @step('wait for (new )?(\w+) stdout message (\w+)')
 def wait_for_message(step, process_name, message):
-    world.wait_for_output_lines_stdout(process_name, [message], new is None)
+    world.processes.wait_for_stdout_str(process_name, [message], new)
 
 @step('Given I have no database')
 def given_i_have_no_database(step):

+ 134 - 18
tests/lettuce/features/terrain.py

@@ -11,6 +11,8 @@ from lettuce import *
 import subprocess
 import os.path
 import shutil
+import re
+import time
 
 # This is a list of files that are freshly copied before each scenario
 # The first element is the original, the second is the target that will be
@@ -19,13 +21,139 @@ copylist = [
 ["configurations/example.org.config.orig", "configurations/example.org.config"]
 ]
 
+# class that keeps track of one running process and the files
+# we created for it. This needs to be moved to our framework-framework
+# as it is not specifically for bind10
+class RunningProcess:
+    def __init__(self, step, process_name, args):
+        # set it to none first so destructor won't error if initializer did
+        self.process = None
+        self.step = step
+        self.process_name = process_name
+        self.remove_files_on_exit = True
+        self._create_filenames()
+        self._start_process(args)
+
+    def _start_process(self, args):
+        stderr_write = open(self.stderr_filename, "w")
+        stdout_write = open(self.stdout_filename, "w")
+        self.process = subprocess.Popen(args, 1, None, subprocess.PIPE,
+                                        stdout_write, stderr_write)
+        # open them again, this time for reading
+        self.stderr = open(self.stderr_filename, "r")
+        self.stdout = open(self.stdout_filename, "r")
+
+    def mangle_filename(self, filebase, extension):
+        filebase = re.sub("\s+", "_", filebase)
+        filebase = re.sub("[^a-zA-Z.\-_]", "", filebase)
+        return filebase + "." + extension
+
+    def _create_filenames(self):
+        filebase = self.step.scenario.feature.name + "-" +\
+                   self.step.scenario.name + "-" + self.process_name
+        self.stderr_filename = self.mangle_filename(filebase, "stderr")
+        self.stdout_filename = self.mangle_filename(filebase, "stdout")
+
+    def stop_process(self):
+        if self.process is not None:
+            self.process.terminate()
+            self.process.wait()
+        self.process = None
+        if self.remove_files_on_exit:
+            self._remove_files()
+
+    def _remove_files(self):
+        os.remove(self.stderr_filename)
+        os.remove(self.stdout_filename)
+
+    def _wait_for_output_str(self, filename, running_file, strings, only_new):
+        if not only_new:
+            full_file = open(filename, "r")
+            for line in full_file:
+                for string in strings:
+                    if line.find(string) != -1:
+                        full_file.close()
+                        return string
+        while True:
+            where = running_file.tell()
+            line = running_file.readline()
+            if line:
+                for string in strings:
+                    if line.find(string) != -1:
+                        return string
+            else:
+                time.sleep(0.5)
+                running_file.seek(where)
+
+    def wait_for_stderr_str(self, strings, only_new = True):
+        return self._wait_for_output_str(self.stderr_filename, self.stderr,
+                                         strings, only_new)
+
+    def wait_for_stdout_str(self, strings, only_new = True):
+        return self._wait_for_output_str(self.stdout_filename, self.stdout,
+                                         strings, only_new)
+
+# Container class for a number of running processes
+# i.e. servers like bind10, etc
+# one-shot programs like dig or bindctl are started and closed separately
+class RunningProcesses:
+    def __init__(self):
+        self.processes = {}
+    
+    def add_process(self, step, process_name, args):
+        assert process_name not in self.processes,\
+            "Process " + name + " already running"
+        self.processes[process_name] = RunningProcess(step, process_name, args)
+
+    def get_process(self, process_name):
+        assert process_name in self.processes,\
+            "Process " + name + " unknown"
+        return self.processes[process_name]
+
+    def stop_process(self, process_name):
+        assert process_name in self.processes,\
+            "Process " + name + " unknown"
+        self.processes[process_name].stop_process()
+        del self.processes[process_name]
+        
+    def stop_all_processes(self):
+        for process in self.processes.values():
+            process.stop_process()
+    
+    def keep_files(self):
+        for process in self.processes.values():
+            process.remove_files_on_exit = False
+
+    def wait_for_stderr_str(self, process_name, strings, only_new = True):
+        """Wait for any of the given strings in the given processes stderr 
+        output. If only_new is True, it will only look at the lines that are 
+        printed to stderr since the last time this method was called. If 
+        False, it will also look at the previously printed lines. This will 
+        block until one of the strings is found. TODO: we may want to put in 
+        a timeout for this... Returns the string that is found"""
+        assert process_name in self.processes,\
+           "Process " + process_name + " unknown"
+        return self.processes[process_name].wait_for_stderr_str(strings,
+                                                                only_new)
+
+    def wait_for_stdout_str(self, process_name, strings, only_new = True):
+        """Wait for any of the given strings in the given processes stderr 
+        output. If only_new is True, it will only look at the lines that are 
+        printed to stderr since the last time this method was called. If 
+        False, it will also look at the previously printed lines. This will 
+        block until one of the strings is found. TODO: we may want to put in 
+        a timeout for this... Returns the string that is found"""
+        assert process_name in self.processes,\
+           "Process " + process_name + " unknown"
+        return self.processes[process_name].wait_for_stdout_str(strings,
+                                                                only_new)
+
 @before.each_scenario
 def initialize(feature):
-    # just make sure our cleanup won't fail if we never did
-    # run the bind10 instance
-    world.processes = {}
-    world.processes_stdout = {}
-    world.processes_stderr = {}
+    # Keep track of running processes
+    world.processes = RunningProcesses()
+
+    # Convenience variable to access the last query result from querying.py
     world.last_query_result = None
 
     # Some tests can modify the settings. If the tests fail half-way, or
@@ -37,16 +165,4 @@ def initialize(feature):
 @after.each_scenario
 def cleanup(feature):
     # Stop any running processes we may have had around
-    for name in world.processes:
-        world.processes[name].terminate()
-        world.processes[name].wait()
-        world.processes_stdout[name] = []
-        world.processes_stderr[name] = []
-
-@world.absorb
-def stop_process(process_name):
-    if process_name in world.processes:
-        p = world.processes[process_name]
-        p.terminate()
-        p.wait()
-        del world.processes[process_name]
+    world.processes.stop_all_processes()