def test_execute_command_in_project_does_not_choke_on_weird_command_output(self):
        some_weird_output = b'\xbf\xe2\x98\x82'  # the byte \xbf is invalid unicode
        self.mock_popen.communicate.return_value = (some_weird_output, None)
        self.mock_popen.returncode = (some_weird_output, None)

        project_type = ProjectType()
        project_type.execute_command_in_project('fake command')
    def test_timing_out_will_break_out_of_command_execution_wait_loop_and_kill_subprocesses(
            self):
        mock_time = self.patch('time.time')
        mock_time.side_effect = [
            0.0, 100.0, 200.0, 300.0
        ]  # time increases by 100 seconds with each loop
        expected_return_code = 1
        self._simulate_hanging_popen_process(
            fake_returncode=expected_return_code)
        self.mock_popen.pid = 55555
        self._mock_stdout_and_stderr(b'fake output', b'fake error')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project(
            command='sleep 99', timeout=250)

        self.assertEqual(
            self.mock_killpg.call_count, 1,
            'os.killpg should be called when execution times out.')
        self.assertEqual(actual_output, 'fake output\nfake error',
                         'Output should contain stdout and stderr.')
        self.assertEqual(actual_return_code, expected_return_code,
                         'Actual return code should match expected.')
        self.assertTrue(
            all([file.close.called for file in self.mock_temporary_files]),
            'All created TemporaryFiles should be closed so that they are removed from the filesystem.'
        )
    def test_command_exiting_normally_will_break_out_of_command_execution_wait_loop(
            self):
        timeout_exc = TimeoutExpired(cmd=None, timeout=1)
        expected_return_code = 0
        # Simulate Popen.wait() timing out twice before command completes and returns output.
        self.mock_popen.wait.side_effect = [
            timeout_exc, timeout_exc, expected_return_code
        ]
        self.mock_popen.returncode = expected_return_code
        self._mock_stdout_and_stderr(b'fake_output', b'fake_error')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project(
            'echo The power is yours!')

        self.assertEqual(
            self.mock_killpg.call_count, 0,
            'os.killpg should not be called when command exits normally.')
        self.assertEqual(actual_output, 'fake_output\nfake_error',
                         'Output should contain stdout and stderr.')
        self.assertEqual(actual_return_code, expected_return_code,
                         'Actual return code should match expected.')
        self.assertTrue(
            all([file.close.called for file in self.mock_temporary_files]),
            'All created TemporaryFiles should be closed so that they are removed from the filesystem.'
        )
    def test_exception_raised_while_waiting_causes_termination_and_adds_error_message_to_output(
            self):
        exception_message = 'Something terribly horrible just happened!'
        value_err_exc = ValueError(exception_message)
        timeout_exc = TimeoutExpired(cmd=None, timeout=1)
        fake_failing_return_code = -15
        # Simulate Popen.wait() timing out twice before raising a ValueError exception.
        self.mock_popen.wait.side_effect = [
            timeout_exc, timeout_exc, value_err_exc, fake_failing_return_code
        ]
        self.mock_popen.returncode = fake_failing_return_code
        self.mock_popen.pid = 55555
        self._mock_stdout_and_stderr(b'', b'')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project(
            'echo The power is yours!')

        self.assertEqual(
            self.mock_killpg.call_count, 1,
            'os.killpg should be called when wait() raises exception.')
        self.assertIn(
            exception_message, actual_output,
            'ClusterRunner exception message should be included in output.')
        self.assertEqual(actual_return_code, fake_failing_return_code,
                         'Actual return code should match expected.')
    def test_teardown_build_runs_teardown(self):
        job_config = JobConfig("name", "setup", "teardown", "command", "atomizer", 10, 10)
        project_type = ProjectType()
        project_type.job_config = MagicMock(return_value=job_config)
        project_type.execute_command_in_project = MagicMock(return_value=("", 0))

        project_type.teardown_build()

        project_type.execute_command_in_project.assert_called_with("teardown", timeout=None)
    def test_teardown_build_runs_teardown(self):
        job_config = JobConfig('name', 'setup', 'teardown', 'command', 'atomizer', 10, 10)
        project_type = ProjectType()
        project_type.job_config = MagicMock(return_value=job_config)
        project_type.execute_command_in_project = MagicMock(return_value=('', 0))

        project_type.teardown_build()

        project_type.execute_command_in_project.assert_called_with('teardown', timeout=None)
    def test_teardown_build_runs_teardown(self):
        job_config = JobConfig('name', 'setup', 'teardown', 'command', 'atomizer', 10)
        project_type = ProjectType()
        project_type.job_config = MagicMock(return_value=job_config)
        project_type.execute_command_in_project = MagicMock(return_value=('', 0))

        project_type.teardown_build()

        project_type.execute_command_in_project.assert_called_with('teardown')
    def test_failing_exit_code_is_injected_when_no_return_code_available(self):
        self.mock_popen.returncode = None  # This will happen if we were not able to terminate the process.
        self._mock_stdout_and_stderr(b'', b'')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project('echo The power is yours!')

        self.assertIsInstance(actual_return_code, int, 'Returned exit code should always be an int.')
        self.assertNotEqual(actual_return_code, 0, 'Returned exit code should be failing (non-zero) when subprocess '
                                                   'returncode is not available.')
    def test_execute_command_in_project_does_not_choke_on_weird_command_output(self):
        some_weird_output = b'\xbf\xe2\x98\x82'  # the byte \xbf is invalid unicode
        self._mock_stdout_and_stderr(some_weird_output, b'')
        self.mock_popen.returncode = 0

        project_type = ProjectType()
        actual_output, _ = project_type.execute_command_in_project('fake command')

        # Part of this test is just proving that no exception is raised on invalid output.
        self.assertIsInstance(actual_output, str, 'Invalid output from a process should still be converted to string.')
    def test_failing_exit_code_is_injected_when_no_return_code_available(self):
        self.mock_popen.returncode = None  # This will happen if we were not able to terminate the process.
        self._mock_stdout_and_stderr(b'', b'')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project('echo The power is yours!')

        self.assertIsInstance(actual_return_code, int, 'Returned exit code should always be an int.')
        self.assertNotEqual(actual_return_code, 0, 'Returned exit code should be failing (non-zero) when subprocess '
                                                   'returncode is not available.')
    def test_execute_command_in_project_does_not_choke_on_weird_command_output(self):
        some_weird_output = b"\xbf\xe2\x98\x82"  # the byte \xbf is invalid unicode
        self._mock_stdout_and_stderr(some_weird_output, b"")
        self.mock_popen.returncode = 0

        project_type = ProjectType()
        actual_output, _ = project_type.execute_command_in_project("fake command")

        # Part of this test is just proving that no exception is raised on invalid output.
        self.assertIsInstance(actual_output, str, "Invalid output from a process should still be converted to string.")
    def test_command_exiting_normally_will_break_out_of_command_execution_wait_loop(self):
        # Simulate Popen.communicate() timing out twice before command completes and returns output.
        timeout_exc = TimeoutExpired(None, 1)
        self.mock_popen.communicate.side_effect = [timeout_exc, timeout_exc, (b'fake_output', b'fake_error')]
        self.mock_popen.returncode = 0
        self.mock_popen.pid = 55555

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project('echo The power is yours!')

        self.assertEqual(self.mock_killpg.call_count, 0, 'os.killpg should not be called when command exits normally.')
        self.assertEqual(actual_output, 'fake_output\nfake_error', 'Output should contain stdout and stderr.')
    def test_teardown_build_executes_teardown_command(self, expected_timeout):
        project_type = ProjectType()
        mock_execute = MagicMock(return_value=('fake output', 0))
        project_type.execute_command_in_project = mock_execute
        project_type.job_config = MagicMock()

        if expected_timeout:
            project_type.teardown_build(timeout=expected_timeout)
        else:
            project_type.teardown_build()

        mock_execute.assert_called_once_with(ANY, timeout=expected_timeout)
    def test_teardown_build_executes_teardown_command(self, expected_timeout):
        project_type = ProjectType()
        mock_execute = MagicMock(return_value=("fake output", 0))
        project_type.execute_command_in_project = mock_execute
        project_type.job_config = MagicMock()

        if expected_timeout:
            project_type.teardown_build(timeout=expected_timeout)
        else:
            project_type.teardown_build()

        mock_execute.assert_called_once_with(ANY, timeout=expected_timeout)
    def test_timing_out_will_break_out_of_command_execution_wait_loop_and_kill_subprocesses(self):
        mock_time = self.patch('time.time')
        mock_time.side_effect = [0.0, 100.0, 200.0, 300.0]  # time increases by 100 seconds with each loop
        self._mock_out_popen_communicate()
        project_type = ProjectType()

        actual_output, actual_return_code = project_type.execute_command_in_project(
            command='sleep 99',
            timeout=250,
        )

        self.assertEqual(self.mock_killpg.call_count, 1, 'os.killpg should be called when execution times out.')
        self.assertEqual(actual_output, 'fake output\nfake error', 'Output should contain stdout and stderr.')
    def test_command_exiting_normally_will_break_out_of_command_execution_wait_loop(self):
        timeout_exc = TimeoutExpired(cmd=None, timeout=1)
        expected_return_code = 0
        # Simulate Popen.wait() timing out twice before command completes and returns output.
        self.mock_popen.wait.side_effect = [timeout_exc, timeout_exc, expected_return_code]
        self.mock_popen.returncode = expected_return_code
        self._mock_stdout_and_stderr(b'fake_output', b'fake_error')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project('echo The power is yours!')

        self.assertEqual(self.mock_killpg.call_count, 0, 'os.killpg should not be called when command exits normally.')
        self.assertEqual(actual_output, 'fake_output\nfake_error', 'Output should contain stdout and stderr.')
        self.assertEqual(actual_return_code, expected_return_code, 'Actual return code should match expected.')
        self.assertTrue(all([file.close.called for file in self.mock_temporary_files]),
                        'All created TemporaryFiles should be closed so that they are removed from the filesystem.')
    def test_exception_raised_while_waiting_for_termination_adds_error_message_to_output(self):
        mock_time = self.patch('time.time')
        mock_time.side_effect = [0.0, 100.0, 200.0, 300.0]  # time increases by 100 seconds with each loop
        fake_failing_return_code = -15
        self.mock_popen.pid = 55555
        self._mock_stdout_and_stderr(b'', b'')
        exception_message = 'Something terribly horrible just happened!'
        self._simulate_hanging_popen_process(
            fake_returncode=fake_failing_return_code, wait_exception=ValueError(exception_message))

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project(
            'echo The power is yours!', timeout=250)

        self.assertIn(exception_message, actual_output, 'ClusterRunner exception message should be included in output.')
        self.assertEqual(actual_return_code, fake_failing_return_code, 'Actual return code should match expected.')
    def test_exception_raised_while_waiting_for_termination_adds_error_message_to_output(self):
        mock_time = self.patch('time.time')
        mock_time.side_effect = [0.0, 100.0, 200.0, 300.0]  # time increases by 100 seconds with each loop
        fake_failing_return_code = -15
        self.mock_popen.pid = 55555
        self._mock_stdout_and_stderr(b'', b'')
        exception_message = 'Something terribly horrible just happened!'
        self._simulate_hanging_popen_process(
            fake_returncode=fake_failing_return_code, wait_exception=ValueError(exception_message))

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project(
            'echo The power is yours!', timeout=250)

        self.assertIn(exception_message, actual_output, 'ClusterRunner exception message should be included in output.')
        self.assertEqual(actual_return_code, fake_failing_return_code, 'Actual return code should match expected.')
    def test_timing_out_will_break_out_of_command_execution_wait_loop_and_kill_subprocesses(self):
        mock_time = self.patch('time.time')
        mock_time.side_effect = [0.0, 100.0, 200.0, 300.0]  # time increases by 100 seconds with each loop
        expected_return_code = 1
        self._simulate_hanging_popen_process(fake_returncode=expected_return_code)
        self.mock_popen.pid = 55555
        self._mock_stdout_and_stderr(b'fake output', b'fake error')

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project(
            command='sleep 99', timeout=250)

        self.assertEqual(self.mock_killpg.call_count, 1, 'os.killpg should be called when execution times out.')
        self.assertEqual(actual_output, 'fake output\nfake error', 'Output should contain stdout and stderr.')
        self.assertEqual(actual_return_code, expected_return_code, 'Actual return code should match expected.')
        self.assertTrue(all([file.close.called for file in self.mock_temporary_files]),
                        'All created TemporaryFiles should be closed so that they are removed from the filesystem.')
    def test_exception_raised_while_waiting_causes_termination_and_adds_error_message_to_output(self):
        exception_message = "Something terribly horrible just happened!"
        value_err_exc = ValueError(exception_message)
        timeout_exc = TimeoutExpired(cmd=None, timeout=1)
        fake_failing_return_code = -15
        # Simulate Popen.wait() timing out twice before raising a ValueError exception.
        self.mock_popen.wait.side_effect = [timeout_exc, timeout_exc, value_err_exc, fake_failing_return_code]
        self.mock_popen.returncode = fake_failing_return_code
        self.mock_popen.pid = 55555
        self._mock_stdout_and_stderr(b"", b"")

        project_type = ProjectType()
        actual_output, actual_return_code = project_type.execute_command_in_project("echo The power is yours!")

        self.assertEqual(self.mock_kill.call_count, 1, "os.killpg should be called when wait() raises exception.")
        self.assertIn(exception_message, actual_output, "ClusterRunner exception message should be included in output.")
        self.assertEqual(actual_return_code, fake_failing_return_code, "Actual return code should match expected.")