1# Copyright (C) 2011 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#    * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#    * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#    * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import StringIO
30import logging
31import os
32
33from webkitpy.common.system.executive import ScriptError
34
35_log = logging.getLogger(__name__)
36
37
38class MockProcess(object):
39    def __init__(self, stdout='MOCK STDOUT\n', stderr=''):
40        self.pid = 42
41        self.stdout = StringIO.StringIO(stdout)
42        self.stderr = StringIO.StringIO(stderr)
43        self.stdin = StringIO.StringIO()
44        self.returncode = 0
45
46    def wait(self):
47        return
48
49    def poll(self):
50        # Consider the process completed when all the stdout and stderr has been read.
51        if self.stdout.len != self.stdout.tell() or self.stderr.len != self.stderr.tell():
52            return None
53        return self.returncode
54
55# FIXME: This should be unified with MockExecutive2
56class MockExecutive(object):
57    PIPE = "MOCK PIPE"
58    STDOUT = "MOCK STDOUT"
59
60    @staticmethod
61    def ignore_error(error):
62        pass
63
64    def __init__(self, should_log=False, should_throw=False, should_throw_when_run=None):
65        self._should_log = should_log
66        self._should_throw = should_throw
67        self._should_throw_when_run = should_throw_when_run or set()
68        # FIXME: Once executive wraps os.getpid() we can just use a static pid for "this" process.
69        self._running_pids = {'test-webkitpy': os.getpid()}
70        self._proc = None
71        self.calls = []
72
73    def check_running_pid(self, pid):
74        return pid in self._running_pids.values()
75
76    def running_pids(self, process_name_filter):
77        running_pids = []
78        for process_name, process_pid in self._running_pids.iteritems():
79            if process_name_filter(process_name):
80                running_pids.append(process_pid)
81
82        _log.info("MOCK running_pids: %s" % running_pids)
83        return running_pids
84
85    def run_and_throw_if_fail(self, args, quiet=False, cwd=None, env=None):
86        self.calls.append(args)
87        if self._should_log:
88            env_string = ""
89            if env:
90                env_string = ", env=%s" % env
91            _log.info("MOCK run_and_throw_if_fail: %s, cwd=%s%s" % (args, cwd, env_string))
92        if self._should_throw_when_run.intersection(args):
93            raise ScriptError("Exception for %s" % args, output="MOCK command output")
94        return "MOCK output of child process"
95
96    def command_for_printing(self, args):
97        string_args = map(unicode, args)
98        return " ".join(string_args)
99
100    def run_command(self,
101                    args,
102                    cwd=None,
103                    input=None,
104                    error_handler=None,
105                    return_exit_code=False,
106                    return_stderr=True,
107                    decode_output=False,
108                    env=None,
109                    debug_logging=False):
110
111        self.calls.append(args)
112
113        assert(isinstance(args, list) or isinstance(args, tuple))
114        if self._should_log:
115            env_string = ""
116            if env:
117                env_string = ", env=%s" % env
118            input_string = ""
119            if input:
120                input_string = ", input=%s" % input
121            _log.info("MOCK run_command: %s, cwd=%s%s%s" % (args, cwd, env_string, input_string))
122        output = "MOCK output of child process"
123
124        if self._should_throw_when_run.intersection(args):
125            raise ScriptError("Exception for %s" % args, output="MOCK command output")
126
127        if self._should_throw:
128            raise ScriptError("MOCK ScriptError", output=output)
129        return output
130
131    def cpu_count(self):
132        return 2
133
134    def kill_all(self, process_name):
135        pass
136
137    def kill_process(self, pid):
138        pass
139
140    def popen(self, args, cwd=None, env=None, **kwargs):
141        self.calls.append(args)
142        if self._should_log:
143            cwd_string = ""
144            if cwd:
145                cwd_string = ", cwd=%s" % cwd
146            env_string = ""
147            if env:
148                env_string = ", env=%s" % env
149            _log.info("MOCK popen: %s%s%s" % (args, cwd_string, env_string))
150        if not self._proc:
151            self._proc = MockProcess()
152        return self._proc
153
154    def call(self, args, **kwargs):
155        self.calls.append(args)
156        _log.info('Mock call: %s' % args)
157
158    def run_in_parallel(self, commands):
159        assert len(commands)
160
161        num_previous_calls = len(self.calls)
162        command_outputs = []
163        for cmd_line, cwd in commands:
164            command_outputs.append([0, self.run_command(cmd_line, cwd=cwd), ''])
165
166        new_calls = self.calls[num_previous_calls:]
167        self.calls = self.calls[:num_previous_calls]
168        self.calls.append(new_calls)
169        return command_outputs
170
171    def map(self, thunk, arglist, processes=None):
172        return map(thunk, arglist)
173
174
175class MockExecutive2(MockExecutive):
176    """MockExecutive2 is like MockExecutive except it doesn't log anything."""
177
178    def __init__(self, output='', exit_code=0, exception=None, run_command_fn=None, stderr=''):
179        self._output = output
180        self._stderr = stderr
181        self._exit_code = exit_code
182        self._exception = exception
183        self._run_command_fn = run_command_fn
184        self.calls = []
185
186    def run_command(self,
187                    args,
188                    cwd=None,
189                    input=None,
190                    error_handler=None,
191                    return_exit_code=False,
192                    return_stderr=True,
193                    decode_output=False,
194                    env=None,
195                    debug_logging=False):
196        self.calls.append(args)
197        assert(isinstance(args, list) or isinstance(args, tuple))
198        if self._exception:
199            raise self._exception  # pylint: disable=E0702
200        if self._run_command_fn:
201            return self._run_command_fn(args)
202        if return_exit_code:
203            return self._exit_code
204        if self._exit_code and error_handler:
205            script_error = ScriptError(script_args=args, exit_code=self._exit_code, output=self._output)
206            error_handler(script_error)
207        if return_stderr:
208            return self._output + self._stderr
209        return self._output
210