Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(579)

Side by Side Diff: mojo/tools/mopy/background_app_group.py

Issue 878933003: Move the apptest runner and parts of mopy to the public SDK. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « mojo/tools/mopy/android.py ('k') | mojo/tools/mopy/config.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import logging
7 import os
8 import subprocess
9 import time
10 import sys
11
12 import mopy.paths
13
14 from shutil import copyfileobj, rmtree
15 from signal import SIGTERM
16 from tempfile import mkdtemp, TemporaryFile
17
18
19 class TimeoutError(Exception):
20 """Allows distinction between timeout failures and generic OSErrors."""
21 pass
22
23
24 def _poll_for_condition(
25 condition, max_seconds=10, sleep_interval=0.1, desc='[unnamed condition]'):
26 """Poll until a condition becomes true.
27
28 Arguments:
29 condition: callable taking no args and returning bool.
30 max_seconds: maximum number of seconds to wait.
31 Might bail up to sleep_interval seconds early.
32 sleep_interval: number of seconds to sleep between polls.
33 desc: description put in TimeoutError.
34
35 Returns:
36 The true value that caused the poll loop to terminate.
37
38 Raises:
39 TimeoutError if condition doesn't become true before max_seconds is reached.
40 """
41 start_time = time.time()
42 while time.time() + sleep_interval - start_time <= max_seconds:
43 value = condition()
44 if value:
45 return value
46 time.sleep(sleep_interval)
47
48 raise TimeoutError('Timed out waiting for condition: %s' % desc)
49
50
51 class _BackgroundShell(object):
52 """Manages a mojo_shell instance that listens for external applications."""
53
54 def __init__(self, mojo_shell_path, shell_args=None):
55 """In a background process, run a shell at mojo_shell_path listening
56 for external apps on an instance-specific socket.
57
58 Arguments:
59 mojo_shell_path: path to the mojo_shell binary to run.
60 shell_args: a list of arguments to pass to mojo_shell.
61
62 Raises:
63 a TimeoutError if the shell takes too long to create the socket.
64 """
65 self._tempdir = mkdtemp(prefix='background_shell_')
66 self._socket_path = os.path.join(self._tempdir, 'socket')
67 self._output_file = TemporaryFile()
68
69 shell_command = [mojo_shell_path,
70 '--enable-external-applications=' + self._socket_path]
71 if shell_args:
72 shell_command += shell_args
73 logging.getLogger().debug(shell_command)
74
75 self._shell = subprocess.Popen(shell_command, stdout=self._output_file,
76 stderr=subprocess.STDOUT)
77 _poll_for_condition(lambda: os.path.exists(self._socket_path),
78 desc="External app socket creation.")
79
80
81 def __del__(self):
82 if self._shell:
83 self._shell.terminate()
84 self._shell.wait()
85 if self._shell.returncode != -SIGTERM:
86 copyfileobj(self._output_file, sys.stdout)
87 rmtree(self._tempdir)
88
89
90 @property
91 def socket_path(self):
92 """The path of the socket where the shell is listening for external apps."""
93 return self._socket_path
94
95
96 class BackgroundAppGroup(object):
97 """Manages a group of mojo apps running in the background."""
98
99 def __init__(self, paths, app_urls, shell_args=None):
100 """In a background process, spins up mojo_shell with external
101 applications enabled, passing an optional list of extra arguments.
102 Then, launches apps indicated by app_urls in the background.
103 The apps and shell are automatically torn down upon destruction.
104
105 Arguments:
106 paths: a mopy.paths.Paths object.
107 app_urls: a list of URLs for apps to run via mojo_launcher.
108 shell_args: a list of arguments to pass to mojo_shell.
109
110 Raises:
111 a TimeoutError if the shell takes too long to begin running.
112 """
113 self._shell = _BackgroundShell(paths.mojo_shell_path, shell_args)
114
115 # Run apps defined by app_urls in the background.
116 self._apps = []
117 for app_url in app_urls:
118 launch_command = [
119 paths.mojo_launcher_path,
120 '--shell-path=' + self._shell.socket_path,
121 '--app-url=' + app_url,
122 '--app-path=' + paths.FileFromUrl(app_url),
123 '--vmodule=*/mojo/shell/*=2']
124 logging.getLogger().debug(launch_command)
125 app_output_file = TemporaryFile()
126 self._apps.append((app_output_file,
127 subprocess.Popen(launch_command,
128 stdout=app_output_file,
129 stderr=subprocess.STDOUT)))
130
131
132 def __del__(self):
133 self._StopApps()
134
135
136 def __enter__(self):
137 return self
138
139
140 def __exit__(self, ex_type, ex_value, traceback):
141 self._StopApps()
142
143
144 def _StopApps(self):
145 """Terminate all background apps."""
146 for output_file, app in self._apps:
147 app.terminate()
148 app.wait()
149 if app.returncode != -SIGTERM:
150 copyfileobj(output_file, sys.stdout)
151 self._apps = []
152
153
154 @property
155 def socket_path(self):
156 """The path of the socket where the shell is listening for external apps."""
157 return self._shell._socket_path
OLDNEW
« no previous file with comments | « mojo/tools/mopy/android.py ('k') | mojo/tools/mopy/config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698