summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozprocess/tests/proclaunch.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/mozprocess/tests/proclaunch.py')
-rw-r--r--testing/mozbase/mozprocess/tests/proclaunch.py209
1 files changed, 209 insertions, 0 deletions
diff --git a/testing/mozbase/mozprocess/tests/proclaunch.py b/testing/mozbase/mozprocess/tests/proclaunch.py
new file mode 100644
index 0000000000..c57e2bb12c
--- /dev/null
+++ b/testing/mozbase/mozprocess/tests/proclaunch.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+
+import argparse
+import collections
+import multiprocessing
+import time
+
+from six.moves import configparser
+
+ProcessNode = collections.namedtuple("ProcessNode", ["maxtime", "children"])
+
+
+class ProcessLauncher(object):
+ """Create and Launch process trees specified by a '.ini' file
+
+ Typical .ini file accepted by this class :
+
+ [main]
+ children=c1, 1*c2, 4*c3
+ maxtime=10
+
+ [c1]
+ children= 2*c2, c3
+ maxtime=20
+
+ [c2]
+ children=3*c3
+ maxtime=5
+
+ [c3]
+ maxtime=3
+
+ This generates a process tree of the form:
+ [main]
+ |---[c1]
+ | |---[c2]
+ | | |---[c3]
+ | | |---[c3]
+ | | |---[c3]
+ | |
+ | |---[c2]
+ | | |---[c3]
+ | | |---[c3]
+ | | |---[c3]
+ | |
+ | |---[c3]
+ |
+ |---[c2]
+ | |---[c3]
+ | |---[c3]
+ | |---[c3]
+ |
+ |---[c3]
+ |---[c3]
+ |---[c3]
+
+ Caveat: The section names cannot contain a '*'(asterisk) or a ','(comma)
+ character as these are used as delimiters for parsing.
+ """
+
+ # Unit time for processes in seconds
+ UNIT_TIME = 1
+
+ def __init__(self, manifest, verbose=False):
+ """
+ Parses the manifest and stores the information about the process tree
+ in a format usable by the class.
+
+ Raises IOError if :
+ - The path does not exist
+ - The file cannot be read
+ Raises ConfigParser.*Error if:
+ - Files does not contain section headers
+ - File cannot be parsed because of incorrect specification
+
+ :param manifest: Path to the manifest file that contains the
+ configuration for the process tree to be launched
+ :verbose: Print the process start and end information.
+ Genrates a lot of output. Disabled by default.
+ """
+
+ self.verbose = verbose
+
+ # Children is a dictionary used to store information from the,
+ # Configuration file in a more usable format.
+ # Key : string contain the name of child process
+ # Value : A Named tuple of the form (max_time, (list of child processes of Key))
+ # Where each child process is a list of type: [count to run, name of child]
+ self.children = {}
+
+ cfgparser = configparser.ConfigParser()
+
+ if not cfgparser.read(manifest):
+ raise IOError("The manifest %s could not be found/opened", manifest)
+
+ sections = cfgparser.sections()
+ for section in sections:
+ # Maxtime is a mandatory option
+ # ConfigParser.NoOptionError is raised if maxtime does not exist
+ if "*" in section or "," in section:
+ raise configparser.ParsingError(
+ "%s is not a valid section name. "
+ "Section names cannot contain a '*' or ','." % section
+ )
+ m_time = cfgparser.get(section, "maxtime")
+ try:
+ m_time = int(m_time)
+ except ValueError:
+ raise ValueError(
+ "Expected maxtime to be an integer, specified %s" % m_time
+ )
+
+ # No children option implies there are no further children
+ # Leaving the children option blank is an error.
+ try:
+ c = cfgparser.get(section, "children")
+ if not c:
+ # If children is an empty field, assume no children
+ children = None
+
+ else:
+ # Tokenize chilren field, ignore empty strings
+ children = [
+ [y.strip() for y in x.strip().split("*", 1)]
+ for x in c.split(",")
+ if x
+ ]
+ try:
+ for i, child in enumerate(children):
+ # No multiplicate factor infront of a process implies 1
+ if len(child) == 1:
+ children[i] = [1, child[0]]
+ else:
+ children[i][0] = int(child[0])
+
+ if children[i][1] not in sections:
+ raise configparser.ParsingError(
+ "No section corresponding to child %s" % child[1]
+ )
+ except ValueError:
+ raise ValueError(
+ "Expected process count to be an integer, specified %s"
+ % child[0]
+ )
+
+ except configparser.NoOptionError:
+ children = None
+ pn = ProcessNode(maxtime=m_time, children=children)
+ self.children[section] = pn
+
+ def run(self):
+ """
+ This function launches the process tree.
+ """
+ self._run("main", 0)
+
+ def _run(self, proc_name, level):
+ """
+ Runs the process specified by the section-name `proc_name` in the manifest file.
+ Then makes calls to launch the child processes of `proc_name`
+
+ :param proc_name: File name of the manifest as a string.
+ :param level: Depth of the current process in the tree.
+ """
+ if proc_name not in self.children:
+ raise IOError("%s is not a valid process" % proc_name)
+
+ maxtime = self.children[proc_name].maxtime
+ if self.verbose:
+ print(
+ "%sLaunching %s for %d*%d seconds"
+ % (" " * level, proc_name, maxtime, self.UNIT_TIME)
+ )
+
+ while self.children[proc_name].children:
+ child = self.children[proc_name].children.pop()
+
+ count, child_proc = child
+ for i in range(count):
+ p = multiprocessing.Process(
+ target=self._run, args=(child[1], level + 1)
+ )
+ p.start()
+
+ self._launch(maxtime)
+ if self.verbose:
+ print("%sFinished %s" % (" " * level, proc_name))
+
+ def _launch(self, running_time):
+ """
+ Create and launch a process and idles for the time specified by
+ `running_time`
+
+ :param running_time: Running time of the process in seconds.
+ """
+ elapsed_time = 0
+
+ while elapsed_time < running_time:
+ time.sleep(self.UNIT_TIME)
+ elapsed_time += self.UNIT_TIME
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("manifest", help="Specify the configuration .ini file")
+ args = parser.parse_args()
+
+ proclaunch = ProcessLauncher(args.manifest)
+ proclaunch.run()