def test_sh_captures_output_in_correct_order_with_various_timing(self):
        """Test if output is containing stdout and stderr lines mixed in proper order (as it is defined in shell script)

        Notice: Test is interacting with shell, to reduce possibility of weird behavior it is retried multiple times
        """
        for i in range(1, 100):
            self.maxDiff = None  # unittest setting
            task = InitTask()
            task._io = IO()

            io = IO()
            out = StringIO()

            with io.capture_descriptors(stream=out, enable_standard_out=False):
                task.sh(''' set +e;
                    sleep 0.05;
                    echo "FIRST";
                    sleep 0.05;
                    echo "SECOND" >&2;
                    echo "THIRD";
                    echo "FOURTH" >&2;
                    echo "FIFTH" >&2;
                    echo "SIXTH";
                    echo "SEVENTH" >&2;
                    echo "NINETH";
                    echo "TENTH";
                ''')

            self.assertEqual(
                "FIRST\r\nSECOND\r\nTHIRD\r\nFOURTH\r\nFIFTH\r\nSIXTH\r\nSEVENTH\r\nNINETH\r\nTENTH\r\n",
                out.getvalue())
    def test_sh_producing_large_outputs(self):
        """Process a few megabytes of output and assert that:

        - It will consume not more than 10 megabytes (assuming also output capturing in tests by io.capture_descriptors())
        - The whole output would be printed correctly
        """

        self.maxDiff = None  # unittest setting
        task = InitTask()
        task._io = IO()

        io = IO()
        out = StringIO()
        text = "History isn't made by kings and politicians, it is made by us."

        memory_before = psutil.Process(
            os.getpid()).memory_info().rss / 1024 / 1024

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            task.py('''
for i in range(0, 1024 * 128):
    print("''' + text + '''")
            ''')

        iterations = 1024 * 128
        text_with_newlines_length = len(text) + 2  # \r + \n
        memory_after = psutil.Process(
            os.getpid()).memory_info().rss / 1024 / 1024

        self.assertEqual(iterations * text_with_newlines_length,
                         len(out.getvalue()))
        self.assertLessEqual(
            memory_after - memory_before,
            16,
            msg='Expected less than 16 megabytes of memory usage')
    def test_full_command_is_shown_only_in_debug_output_level(self):
        """Test that sh() will show full bash script only in case, when '-rl debug' is used

        :return:
        """

        task = InitTask()
        task._io = IO()
        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            # CASE 1
            with self.subTest('NORMAL output level'):
                try:
                    task.sh('python3 -m rkd :sh -c "exit 5"')

                except subprocess.CalledProcessError as e:
                    self.assertIn(
                        "Command 'exit 5' returned non-zero exit status 5.",
                        e.output)

            # CASE 2
            with self.subTest('DEBUG output level'):
                try:
                    task.sh('python3 -m rkd -rl debug :sh -c "exit 5"')

                except subprocess.CalledProcessError as e:
                    self.assertIn(
                        "Command '#!/bin/bash -eopipefail \r\nset -euo pipefail;"
                        + " exit 5' returned non-zero exit status 5.",
                        e.output)
    def test_sh_captures_output_in_correct_order_with_fixed_timing(self):
        """Test if output contains stdout and stderr lines printed out in proper order,
        while there is a sleep between prints

        Notice: Test is interacting with shell, to reduce possibility of weird behavior it is retried multiple times
        """

        for i in range(1, 30):
            self.maxDiff = None  # unittest setting
            task = InitTask()
            task._io = IO()

            io = IO()
            out = StringIO()

            with io.capture_descriptors(stream=out, enable_standard_out=False):
                task.sh(''' set +e;
                    sleep 0.05;
                    echo "FIRST";
                    sleep 0.05;
                    echo "SECOND" >&2;
                    sleep 0.05;
                    echo "THIRD";
                ''')

            self.assertEqual("FIRST\r\nSECOND\r\nTHIRD\r\n", out.getvalue())
    def test_sh_rkd_in_rkd_shows_first_lines_on_error(self):
        """Bugfix: sh() was loosing first line(s) of output, when exception was raised

        Notice: Test is interacting with shell, to reduce possibility of weird behavior it is retried multiple times
        """

        for i in range(1, 5):
            for std_redirect in ['', '>&2']:
                task = InitTask()
                task._io = IO()
                io = IO()
                out = StringIO()

                with io.capture_descriptors(stream=out,
                                            enable_standard_out=False):
                    try:
                        task.sh(''' 
                            python3 -m rkd --silent :sh -c 'echo "Bartolomeo Vanzetti" '''
                                + std_redirect + '''; exit 127'
                        ''')
                    except subprocess.CalledProcessError:
                        pass

                self.assertIn(
                    'Bartolomeo Vanzetti',
                    out.getvalue(),
                    msg='Expected that output will be shown for std_redirect=%s'
                    % std_redirect)
    def test_one_failed_step_is_preventing_next_steps_from_execution_and_result_is_marked_as_failure(self):
        """Check the correctness of error handling"""

        io = IO()
        str_io = StringIO()
        buffered = BufferedSystemIO()

        task_declaration = get_test_declaration()
        BasicTestingCase.satisfy_task_dependencies(task_declaration.get_task_to_execute(), io=buffered)

        ctx = ExecutionContext(task_declaration)
        executor = DeclarativeExecutor()
        executor.add_step('python', 'this.io().outln("Peter Kropotkin"); return True', task_name=':first', rkd_path='', envs={})
        executor.add_step('bash', 'echo "Buenaventura Durruti"; exit 1', task_name=':second', rkd_path='', envs={})
        executor.add_step('python', 'this.io().outln("This one will not show"); return True', task_name=':third', rkd_path='', envs={})

        with io.capture_descriptors(target_files=[], stream=str_io, enable_standard_out=False):
            final_result = executor.execute_steps_one_by_one(ctx, task_declaration.get_task_to_execute())

        output = str_io.getvalue() + buffered.get_value()

        self.assertIn('Peter Kropotkin', output)
        self.assertIn('Buenaventura Durruti', output)
        self.assertNotIn('This one will not show', output)
        self.assertEqual(False, final_result)
Beispiel #7
0
    def run_and_capture_output(self,
                               argv: list,
                               verbose: bool = False) -> Tuple[str, int]:
        """
        Run task(s) and capture output + exit code.
        Whole RKD from scratch will be bootstrapped there.

        Example usage:
            full_output, exit_code = self.run_and_capture_output([':tasks'])

        :param list argv: List of tasks, arguments, commandline switches
        :param bool verbose: Print all output also to stdout

        :return:
        """

        io = IO()
        out = StringIO()
        exit_code = 0

        try:
            with io.capture_descriptors(stream=out,
                                        enable_standard_out=verbose):
                app = RiotKitDoApplication()
                app.main(['test_functional.py'] + argv)

        except SystemExit as e:
            self._restore_standard_out()
            exit_code = e.code

        return out.getvalue(), exit_code
    def test_functional_hooks_are_executed_when_exists_and_files_with_extension_only_are_skipped(
            self):
        """Given we have an example hooks in pre-upgrade/whoami.sh and in post-upgrade/history.sh
        And we try to run those hooks using hooks_executed()
        Then we will see output produced by those scripts
        And .dotfiles will be ignored
        """

        self._prepare_test_data()

        buffer = StringIO()
        hooks_capturing_io = IO()

        task = TestTask()
        task._io = BufferedSystemIO()
        ctx = ExecutionContext(TaskDeclaration(task), args={}, env={})

        with hooks_capturing_io.capture_descriptors(stream=buffer,
                                                    enable_standard_out=True):
            with task.hooks_executed(ctx, 'upgrade'):
                pass

        self.assertIn('>> This is a whoami.sh hook, test:',
                      buffer.getvalue(),
                      msg='Expected pre-upgrade hook to be ran')

        self.assertIn('25 June 1978 the rainbow flag was first flown',
                      buffer.getvalue(),
                      msg='Expected post-upgrade hook to be ran')

        self.assertIn('pre-upgrade/whoami.sh', task._io.get_value())
        self.assertNotIn('.gitkeep', task._io.get_value())
    def test_bash_case_verify_env_variables_are_present(self):

        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            self._create_callable_tester('echo "Boolean: ${ARG_TEST}, Text: ${ARG_MESSAGE}"', language='bash')

        self.assertIn('ARG_TEST: unbound variable', out.getvalue())
Beispiel #10
0
    def test_quotes_are_escaped_in_shell_commands(self):
        task = InitTask()
        task._io = IO()
        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            task.sh('echo ${NAME}', env={'NAME': 'Ferdinando "Nicola" Sacco'})

        self.assertIn('Ferdinando "Nicola" Sacco', out.getvalue())
Beispiel #11
0
    def execute_mocked_task_and_get_output(self,
                                           task: TaskInterface,
                                           args=None,
                                           env=None) -> str:
        """
        Run a single task, capturing it's output in a simplified way.
        There is no whole RKD bootstrapped in this operation.

        :param TaskInterface task:
        :param dict args:
        :param dict env:
        :return:
        """

        if args is None:
            args = {}

        if env is None:
            env = {}

        ctx = ApplicationContext([], [], '')
        ctx.io = BufferedSystemIO()

        task.internal_inject_dependencies(
            io=ctx.io,
            ctx=ctx,
            executor=OneByOneTaskExecutor(ctx=ctx),
            temp_manager=TempManager())

        merged_env = deepcopy(os.environ)
        merged_env.update(env)

        r_io = IO()
        str_io = StringIO()

        defined_args = {}

        for arg, arg_value in args.items():
            defined_args[arg] = {'default': ''}

        with r_io.capture_descriptors(enable_standard_out=True, stream=str_io):
            try:
                # noinspection PyTypeChecker
                result = task.execute(
                    ExecutionContext(TaskDeclaration(task),
                                     args=args,
                                     env=merged_env,
                                     defined_args=defined_args))
            except Exception:
                self._restore_standard_out()
                print(ctx.io.get_value() + "\n" + str_io.getvalue())
                raise

        return ctx.io.get_value() + "\n" + str_io.getvalue(
        ) + "\nTASK_EXIT_RESULT=" + str(result)
    def test_bash_successful_case(self):
        """ Bash callable test: Successful case """

        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            self._create_callable_tester('python --version', language='bash')

        self.assertIn("Python", out.getvalue())
        self.assertTrue(out.getvalue(), msg='python --version should result with a True')
    def test_ps(self):
        """Simply check if docker-compose ps can be executed, if the standard switches are correct"""

        io_str = StringIO()
        io = IO()

        with io.capture_descriptors(stream=io_str, enable_standard_out=False):
            drv = self._get_prepared_compose_driver()
            drv.ps([])

        self.assertIn('Name', io_str.getvalue())
        self.assertIn('Ports', io_str.getvalue())
    def test_io_capturing_is_restoring_both_stdout_and_stderr_to_previous_state(
            self):
        """Assert that capture_descriptors() restores sys.stdout and sys.stderr to original state after
        mocking them for output capturing"""

        io = IO()

        stdout_backup = sys.stdout
        stderr_backup = sys.stderr

        with io.capture_descriptors(target_files=None):
            pass

        self.assertEqual(stdout_backup, sys.stdout)
        self.assertEqual(stderr_backup, sys.stderr)
Beispiel #15
0
    def test_environment_is_passed_and_system_environment_still_available(
            self) -> None:
        os.environ['COMING_FROM_PARENT_CONTEXT'] = 'Buenaventura Durruti'

        io = IO()
        out = StringIO()

        try:
            with io.capture_descriptors(stream=out, enable_standard_out=False):
                check_call('env', env={'PROTEST_TYPE': 'Sabotage'})

        finally:
            del os.environ['COMING_FROM_PARENT_CONTEXT']

        self.assertIn('PROTEST_TYPE=Sabotage', out.getvalue())
        self.assertIn('COMING_FROM_PARENT_CONTEXT=Buenaventura Durruti',
                      out.getvalue())
Beispiel #16
0
    def test_dollar_symbols_are_escaped_in_shell_commands(self):
        """Check that in envrionment variable there can be defined a value that contains dollar symbols"""

        task = InitTask()
        task._io = IO()
        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            task.sh('env | grep TEST_ENV',
                    env={
                        'TEST_ENV': "Mikhail\\$1Bakunin\\$PATHtest",
                        'TEST_ENV_NOT_ESCAPED': 'Hello $TEST_ENV'
                    })

        self.assertIn('TEST_ENV=Mikhail$1Bakunin$PATHtest', out.getvalue())
        self.assertIn('TEST_ENV_NOT_ESCAPED=Hello Mikhail$1Bakunin$PATHtest',
                      out.getvalue())
Beispiel #17
0
    def test_non_interactive_session_returns_output(self):
        """Checks functionally if process.py is implementing a fall-back for non-interactive sessions

        'true |' part enforces the console to be non-interactive, which should cause
        "termios.error: (25, 'Inappropriate ioctl for device')" that should be handled and interactive mode
        should be turned off for stdin
        """

        task = InitTask()
        task._io = IO()
        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            task.sh(
                ''' true | python3 -m rkd --silent :sh -c 'echo "Strajk Kobiet! Jebac PiS!"' '''
            )

        self.assertIn('Strajk Kobiet! Jebac PiS!', out.getvalue())
    def test_return_false_is_added_to_the_code(self):
        """Assert that code in Python without a "return" will have "return false" by default

        Previously, when return was not added automatically there was an error raised (not always - depending on the code), example:
            AttributeError: 'Try' object has no attribute 'value'

        https://github.com/riotkit-org/riotkit-do/issues/37
        :return:
        """

        io = IO()
        out = StringIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            returned_value = self._create_callable_tester(
                '''print('History isn't made by kings and politicians, it is made by us.');''', language='python')

        self.assertIn("History isn't made by kings and politicians, it is made by us.", out.getvalue())
        self.assertFalse(returned_value)
    def test_executing_multiple_steps_one_by_one_the_order_is_preserved(self):
        """Assert that execution order is preserved - as we register steps"""

        io = IO()
        str_io = StringIO()

        task_declaration = get_test_declaration()
        BasicTestingCase.satisfy_task_dependencies(task_declaration.get_task_to_execute(), io=io)

        ctx = ExecutionContext(task_declaration)
        executor = DeclarativeExecutor()
        executor.add_step('python', 'this.io().outln("First"); return True', task_name=':first', rkd_path='', envs={})
        executor.add_step('bash', 'echo "Second"; exit 0', task_name=':second', rkd_path='', envs={})
        executor.add_step('python', 'this.io().outln("Third"); return True', task_name=':third', rkd_path='', envs={})

        with io.capture_descriptors(target_files=[], stream=str_io, enable_standard_out=False):
            executor.execute_steps_one_by_one(ctx, task_declaration.get_task_to_execute())

        self.assertEqual("First\nSecond\r\nThird\n", str_io.getvalue())
    def test_functional_execute_hooks_executes_post_upgrade_hooks(self):
        """Assert that post-upgrade/history.sh is executed"""

        self._prepare_test_data()

        buffer = StringIO()
        hooks_capturing_io = IO()

        task = TestTask()
        task._io = BufferedSystemIO()
        ctx = ExecutionContext(TaskDeclaration(task), args={}, env={})

        with hooks_capturing_io.capture_descriptors(stream=buffer,
                                                    enable_standard_out=True):
            task.execute_hooks(ctx, 'post-upgrade')

        self.assertIn('25 June 1978 the rainbow flag was first flown',
                      buffer.getvalue(),
                      msg='Expected post-upgrade hook to be ran')
Beispiel #21
0
    def test_parse_tasks_successful_case(self):
        """Successful case with description, arguments and bash steps
        """

        input_tasks = {
            ':resistentia': {
                'description':
                'Against moving the costs of the crisis to the workers!',
                'arguments': {
                    '--picket': {
                        'help': 'Picket form',
                        'required': False,
                        'action': 'store_true'
                    }
                },
                'steps': ['echo "Resistentia!"']
            }
        }

        io = IO()
        out = StringIO()
        factory = YamlSyntaxInterpreter(io, YamlFileLoader([]))
        parsed_tasks = factory.parse_tasks(input_tasks, '', './makefile.yaml',
                                           {})

        self.assertEqual(':resistentia',
                         parsed_tasks[0].to_full_name(),
                         msg='Expected that the task name will be present')

        declaration = parsed_tasks[0]
        declaration.get_task_to_execute()._io = NullSystemIO()

        with io.capture_descriptors(stream=out, enable_standard_out=False):
            declaration.get_task_to_execute().execute(
                ExecutionContext(declaration))

        self.assertIn('Resistentia!',
                      out.getvalue(),
                      msg='Expected that echo contents will be visible')
Beispiel #22
0
    def test_sh_3rd_depth_rkd_calls(self):
        """Bugfix: sh() of 3-depth calls -> test -> rkd -> rkd returns first line of output
        """

        for std_redirect in ['', '>&2']:
            task = InitTask()
            task._io = IO()
            io = IO()
            out = StringIO()

            with io.capture_descriptors(stream=out, enable_standard_out=False):
                try:
                    task.sh(''' 
                        python3 -m rkd --silent :sh -c 'python -m rkd --silent :sh -c \'echo "Nicola Sacco" '''
                            + std_redirect + '''; exit 1\''
                    ''')
                except subprocess.CalledProcessError:
                    pass

            self.assertIn(
                'Nicola Sacco',
                out.getvalue(),
                msg='Expected that output will be shown for std_redirect=%s' %
                std_redirect)