def test_compute_subjobs_for_build_only_atomizes_if_override_not_specified(
            self, atoms_override, atomizer_output, atomizer_called):
        """
        :type atoms_override: list[str] | None
        :type atomizer_output: list[Atom] | None
        :type atomizer_called: bool
        """
        self.patch('os.path.isfile').return_value = False
        mock_project = Mock(spec_set=ProjectType())
        mock_project.atoms_override = atoms_override
        mock_project.timing_file_path.return_value = '/some/path/doesnt/matter'
        mock_project.project_directory = '/some/project/directory'
        mock_atomizer = Mock(spec_set=Atomizer)
        mock_atomizer.atomize_in_project.return_value = atomizer_output
        mock_job_config = Mock(spec=JobConfig)
        mock_job_config.name = 'some_config'
        mock_job_config.max_executors = 1
        mock_job_config.atomizer = mock_atomizer

        subjob_calculator = SubjobCalculator()
        subjob_calculator.compute_subjobs_for_build(build_id=1,
                                                    job_config=mock_job_config,
                                                    project_type=mock_project)

        self.assertEquals(mock_atomizer.atomize_in_project.called,
                          atomizer_called)
    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_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_calling_kill_subprocesses_will_break_out_of_command_execution_wait_loop(
            self):
        self._mock_stdout_and_stderr(b'fake_output', b'fake_error')
        self.mock_popen.pid = 55555
        self._simulate_hanging_popen_process()

        project_type = ProjectType()
        command_thread = SafeThread(
            target=project_type.execute_command_in_project,
            args=('echo The power is yours!', ))

        # This calls execute_command_in_project() on one thread, and calls kill_subprocesses() on another. The
        # kill_subprocesses() call should cause the first thread to exit.
        command_thread.start()
        project_type.kill_subprocesses()

        # This *should* join immediately, but we specify a timeout just in case something goes wrong so that the test
        # doesn't hang. A successful join implies success. We also use the UnhandledExceptionHandler so that exceptions
        # propagate from the child thread to the test thread and fail the test.
        with UnhandledExceptionHandler.singleton():
            command_thread.join(timeout=10)
            if command_thread.is_alive():
                self.mock_killpg(
                )  # Calling killpg() causes the command thread to end.
                self.fail(
                    'project_type.kill_subprocesses should cause the command execution wait loop to exit.'
                )

        self.mock_killpg.assert_called_once_with(
            55555, ANY)  # Note: os.killpg does not accept keyword args.
    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_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_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_job_config_uses_passed_in_config_instead_of_clusterrunner_yaml(self):
        config_dict = {
            'commands': ['shell command 1', 'shell command 2;'],
            'atomizers': [{'TESTPATH': 'atomizer command'}],
            'max_executors': 100,
            'max_executors_per_slave': 2,
        }
        project_type = ProjectType(config=config_dict, job_name='some_job_name')

        job_config = project_type.job_config()

        self.assertEquals(job_config.name, 'some_job_name')
        self.assertEquals(job_config.command, 'shell command 1 && shell command 2')
        self.assertEquals(job_config.max_executors, 100)
        self.assertEquals(job_config.max_executors_per_slave, 2)
    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.')
Exemple #12
0
 def _create_mock_project_type(self):
     return MagicMock(spec_set=ProjectType())