Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create a new command to run extra tutor commands #59

Merged
merged 15 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions features/run_extra_commands.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Feature: Run extra commands
@fixture.behave.tutor_root
Scenario: Execute the extra commands from config.yml properly
Given There is a tutor root
And There is a config.yml file
And There are valid commands defined
When I write the command tutor distro run-extra-commands and commands will be properly executed

@fixture.behave.tutor_root
Scenario: Execute commands that are not valid
Given There is a tutor root
And There is a config.yml file
And There are invalid commands defined
When I write the command tutor distro run-extra-commands and commands execution will fail
64 changes: 64 additions & 0 deletions features/steps/run_extra_commands_step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os

import subprocess

from behave import given, when, then # pylint: disable=no-name-in-module
from click.testing import CliRunner
from tutor import config as tutor_config

from tutordistro.commands.run_extra_commands import run_extra_commands


@given("There are valid commands defined")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
extra_commands = [
"tutor plugins update",
"tutor plugins install forum",
"tutor plugins enable forum"
]

config = context.scenario.config
config.update({
"DISTRO_EXTRA_COMMANDS": extra_commands
})

tutor_config.save_config_file(context.scenario.tutor_root, config)
config = tutor_config.load(context.scenario.tutor_root)
context.scenario.config = config
context.scenario.extra_commands = "DISTRO_EXTRA_COMMANDS"

assert "DISTRO_EXTRA_COMMANDS" in config


@given("There are invalid commands defined")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
extra_commands = [
"pip install application"
]

config = context.scenario.config
config.update({
"DISTRO_EXTRA_COMMANDS": extra_commands
})

tutor_config.save_config_file(context.scenario.tutor_root, config)
config = tutor_config.load(context.scenario.tutor_root)
context.scenario.config = config
context.scenario.extra_commands = "DISTRO_EXTRA_COMMANDS"

assert "DISTRO_EXTRA_COMMANDS" in config


@when("I write the command tutor distro run-extra-commands and commands will be properly executed")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
runner = CliRunner()
result = runner.invoke(run_extra_commands, obj=context)
assert result.exit_code == 0


@when("I write the command tutor distro run-extra-commands and commands execution will fail")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
runner = CliRunner()
result = runner.invoke(run_extra_commands, obj=context)
assert result.exit_code != 0

Empty file.
Empty file.
95 changes: 95 additions & 0 deletions tests/distro/run_extra_commands/application/test_run_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Test run commands application.
"""

import pytest

from tests.distro.run_extra_commands.infrastructure.test_tutor_commands import TestTutorCommandManager
from tutordistro.distro.extra_commands.application.commands_runner import CommandsRunner
from tutordistro.distro.share.domain.command_error import CommandError


def test_valid_tutor_command():
"""
Test running valid commands.

This test verifies that are executed all the extra commands successfully.
"""
# Given
valid_tutor_commands = [
"command with word tutor 1",
"command with word tutor 2",
"command with word tutor 3",
]

tutor_commands_manager = TestTutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=valid_tutor_commands)

# When
for command in valid_tutor_commands:
run_tutor_command(command=command)

assert tutor_commands_manager.commands_ran == len(valid_tutor_commands)


def test_invalid_or_misspelled_tutor_command():
"""
Test running invalid commands.

This test verifies that the execution fails when is
intended to execute invalid extra commands.
"""
# Given
invalid_tutor_command = [
"pip command 1",
"tutor command && pip command 2",
"tutor command & pip command 3",
"tutor command || pip command 4",
"tutor command | pip command 5",
"tutor command ; pip command 6",
]

with pytest.raises(CommandError) as command_error:
tutor_commands_manager = TestTutorCommandManager()
CommandsRunner(commands_manager=tutor_commands_manager, commands=invalid_tutor_command)

assert command_error.type is CommandError

splitted_commands = [tutor_commands_manager.split_command(command) for command in invalid_tutor_command]
commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ")

pip_commands_sent = commands_word_by_word.count("pip")
pip_commands_found = command_error.value.args[0].split(" ").count("pip")

assert pip_commands_sent == pip_commands_found


def test_misspelled_tutor_command():
"""
Test running misspelled Tutor commands.

This test verifies that is warned the user of trying to execute
a misspelled Tutor command.
"""
# Given
misspelled_commands = [
"totur command 1",
"totur command 2",
"totur command 3",
"totur command 4",
"totur command 5",
]

with pytest.raises(CommandError) as command_error:
tutor_commands_manager = TestTutorCommandManager()
CommandsRunner(commands_manager=tutor_commands_manager, commands=misspelled_commands)

assert command_error.type is CommandError

splitted_commands = [tutor_commands_manager.split_command(command) for command in misspelled_commands]
commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ")

misspelled_commands_sent = commands_word_by_word.count("totur")
misspelled_commands_found = command_error.value.args[0].split(" ").count("totur")

assert misspelled_commands_sent == misspelled_commands_found
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Test tutor commands functions.
"""

from tutordistro.distro.extra_commands.infrastructure.tutor_commands import TutorCommandManager


class TestTutorCommandManager(TutorCommandManager):
"""
Executes a Tutor command for testing.

This class provides functionality to execute extra Tutor commands for testing.

Args:
CommandManager (class): Base command manager class.
"""
commands_ran = 0

def run_command(self, command: str):
"""
This method runs an testing command.

Args:
command (str): Testing command.
"""

self.commands_ran += 1
2 changes: 2 additions & 0 deletions tutordistro/commands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from tutordistro.commands.enable_private_packages import enable_private_packages
from tutordistro.commands.enable_themes import enable_themes
from tutordistro.commands.repository_validator import repository_validator
from tutordistro.commands.run_extra_commands import run_extra_commands
from tutordistro.commands.syntax_validator import syntax_validator


Expand All @@ -23,3 +24,4 @@ def distro() -> None:
distro.add_command(enable_private_packages)
distro.add_command(repository_validator)
distro.add_command(syntax_validator)
distro.add_command(run_extra_commands)
32 changes: 32 additions & 0 deletions tutordistro/commands/run_extra_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Distro run extra commands command.
"""

import subprocess

import click
from tutor import config as tutor_config

from tutordistro.distro.extra_commands.application.commands_runner import CommandsRunner
from tutordistro.distro.extra_commands.infrastructure.tutor_commands import TutorCommandManager


@click.command(name="run-extra-commands", help="Run tutor commands")
def run_extra_commands():
"""
This command runs tutor commands defined in DISTRO_EXTRA_COMMANDS
"""
directory = (
subprocess.check_output("tutor config printroot", shell=True)
.decode("utf-8")
.strip()
)
config = tutor_config.load(directory)
distro_extra_commands = config.get("DISTRO_EXTRA_COMMANDS", None)

tutor_commands_manager = TutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=distro_extra_commands)

if distro_extra_commands:
for command in distro_extra_commands:
run_tutor_command(command=command)
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Distro command runner.
"""
from tutordistro.distro.extra_commands.domain.command_manager import CommandManager


class CommandsRunner:
"""
Command runner.

This class is responsible of executing extra commands by invoking the run_command method
on a commands manager.

Attributes:
commands_manager (ThemeRepository): The command manager to use for executing the extra command.
"""

def __init__(self, commands_manager: CommandManager, commands: list[str]):
self.commands_manager = commands_manager
commands_manager.validate_commands(commands)

def __call__(self, command: str):
"""
Run the provided command.

This method runs the provided command by invoking the run_command method
from the given command manager

Args:
command (str): Command to execute.
"""

return self.commands_manager.run_command(command=command)
Empty file.
12 changes: 12 additions & 0 deletions tutordistro/distro/extra_commands/domain/command_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Command Manager"""

import abc
from abc import abstractmethod


class CommandManager(metaclass=abc.ABCMeta):
"""Command Manager"""

@abstractmethod
def run_command(self, command: str):
"""Run a command."""
Empty file.
Loading
Loading