Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
129 changes: 128 additions & 1 deletion vulcano/app/lexer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@
from unittest import TestCase

# Third-party imports
from pygments.token import Keyword, Name, Number, Operator, String, Text

# Local imports
from .lexer import create_lexer
from .lexer import (
DraculaTheme,
MonokaiTheme,
NordTheme,
OneDarkTheme,
SolarizedDarkTheme,
VulcanoStyle,
create_lexer,
)


class TestThemesModule(TestCase):
Expand Down Expand Up @@ -44,3 +54,120 @@ def test_dot_path_commands_are_escaped_and_ordered(self):
self.assertEqual(parts[0], r"text\.formal\.dear")
self.assertEqual(parts[1], r"text\.hi")
self.assertEqual(parts[2], "text")

def test_create_lexer_with_no_commands_returns_base_token_count(self):
lexer_none = create_lexer(None)
lexer_empty = create_lexer([])
self.assertEqual(
len(lexer_none.tokens["root"]),
len(lexer_empty.tokens["root"]),
)

def test_each_create_lexer_call_returns_fresh_subclass(self):
lexer_a = create_lexer(["foo"])
lexer_b = create_lexer(["bar"])
self.assertIsNot(lexer_a, lexer_b)

def test_command_keyword_token_is_first_in_root(self):
lexer = create_lexer(["mycmd"])
token_type = lexer.tokens["root"][0][1]
self.assertEqual(token_type, Keyword)

def test_no_commands_does_not_prepend_keyword_token(self):
lexer = create_lexer(None)
first_token_type = lexer.tokens["root"][0][1]
self.assertNotEqual(first_token_type, Keyword)


class TestVulcanoLexerTokenization(TestCase):
def _lex(self, text, commands=None):
lexer_cls = create_lexer(commands or [])
lexer = lexer_cls()
return list(lexer.get_tokens(text))

def test_integer_tokenized_as_number(self):
tokens = self._lex("42")
token_types = [t for t, _ in tokens]
self.assertIn(Number.Integer, token_types)

def test_boolean_true_tokenized_as_operator(self):
tokens = self._lex("True")
token_types = [t for t, _ in tokens]
self.assertIn(Operator, token_types)

def test_boolean_false_tokenized_as_operator(self):
tokens = self._lex("False")
token_types = [t for t, _ in tokens]
self.assertIn(Operator, token_types)

def test_lowercase_boolean_tokenized_as_operator(self):
tokens = self._lex("true false")
token_types = [t for t, _ in tokens]
self.assertIn(Operator, token_types)

def test_double_quoted_string_tokenized_as_string(self):
tokens = self._lex('"hello world"')
token_types = [t for t, _ in tokens]
self.assertIn(String.Single, token_types)

def test_single_quoted_string_tokenized_as_string(self):
tokens = self._lex("'hello'")
token_types = [t for t, _ in tokens]
self.assertIn(String.Single, token_types)

def test_identifier_tokenized_as_name(self):
tokens = self._lex("myvar")
token_types = [t for t, _ in tokens]
self.assertIn(Name, token_types)

def test_registered_command_tokenized_as_keyword(self):
tokens = self._lex("greet", commands=["greet"])
token_types = [t for t, _ in tokens]
self.assertIn(Keyword, token_types)

def test_unregistered_command_not_tokenized_as_keyword(self):
tokens = self._lex("unknown", commands=["greet"])
token_types = [t for t, _ in tokens]
self.assertNotIn(Keyword, token_types)

def test_whitespace_tokenized_as_text(self):
tokens = self._lex(" ")
token_types = [t for t, _ in tokens]
self.assertIn(Text, token_types)


class TestThemeStyles(TestCase):
def test_dracula_theme_has_styles(self):
self.assertTrue(len(DraculaTheme.styles) > 0)

def test_nord_theme_has_styles(self):
self.assertTrue(len(NordTheme.styles) > 0)

def test_solarized_dark_theme_has_styles(self):
self.assertTrue(len(SolarizedDarkTheme.styles) > 0)

def test_one_dark_theme_has_styles(self):
self.assertTrue(len(OneDarkTheme.styles) > 0)

def test_all_themes_are_subclasses_of_vulcano_style(self):
themes = [
MonokaiTheme, DraculaTheme, NordTheme, SolarizedDarkTheme, OneDarkTheme
]
for theme in themes:
self.assertTrue(
issubclass(theme, VulcanoStyle),
"{} not a VulcanoStyle".format(theme.__name__),
)

def test_pygments_style_returns_callable(self):
style = DraculaTheme.pygments_style()
self.assertIsNotNone(style)

def test_keyword_style_defined_in_color_themes(self):
themes = [DraculaTheme, NordTheme, SolarizedDarkTheme, OneDarkTheme]
for theme in themes:
self.assertIn(
Keyword,
theme.styles,
"{} missing Keyword style".format(theme.__name__),
)
45 changes: 45 additions & 0 deletions vulcano/command/builtin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,48 @@ def test_help_without_command(self, console_mock):
help_func = builtin.help(app)
help_func()
console_mock.print.assert_called_once()

@patch(console_path)
def test_help_hides_invisible_commands(self, console_mock):
from rich.table import Table

app = MagicMock()
visible_cmd = MagicMock()
visible_cmd.name = "visible"
visible_cmd.short_description = "I am visible"
visible_cmd.visible = True
hidden_cmd = MagicMock()
hidden_cmd.name = "hidden"
hidden_cmd.short_description = "I am hidden"
hidden_cmd.visible = False
app.manager._commands = {"visible": visible_cmd, "hidden": hidden_cmd}
help_func = builtin.help(app)
help_func()
console_mock.print.assert_called_once()
table_arg = console_mock.print.call_args[0][0]
self.assertIsInstance(table_arg, Table)
# Only the visible command should be added; row count must be 1.
self.assertEqual(table_arg.row_count, 1)

@patch(console_path)
def test_help_empty_command_list(self, console_mock):
from rich.table import Table

app = MagicMock()
app.manager._commands = {}
help_func = builtin.help(app)
help_func()
console_mock.print.assert_called_once()
table_arg = console_mock.print.call_args[0][0]
self.assertIsInstance(table_arg, Table)
self.assertEqual(table_arg.row_count, 0)

@patch(console_path)
def test_exit_prints_goodbye_message(self, console_mock):
app = MagicMock()
app.do_repl = True
exit_func = builtin.exit(app)
exit_func()
console_mock.print.assert_called_once()
printed_text = str(console_mock.print.call_args)
self.assertTrue(len(printed_text) > 0)
140 changes: 140 additions & 0 deletions vulcano/command/docutils_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# -* coding: utf-8 *-
# System imports
from unittest import TestCase

# Local imports
from .docutils import multi_doc_parser


class TestMultiDocParserNullInputs(TestCase):
def test_none_returns_empty_tuple(self):
result = multi_doc_parser(None)
self.assertEqual(result, (None, None, {}, None))

def test_empty_string_returns_empty_tuple(self):
result = multi_doc_parser("")
self.assertEqual(result, (None, None, {}, None))


class TestMultiDocParserPlainProse(TestCase):
def test_plain_short_description_only(self):
short, long, params, returns = multi_doc_parser("Do something useful.")
self.assertEqual(short, "Do something useful.")
self.assertIsNone(long)
self.assertEqual(params, {})
self.assertIsNone(returns)

def test_short_and_long_description_no_params(self):
docstring = "Short line.\n\nLonger explanation here."
short, long, params, returns = multi_doc_parser(docstring)
self.assertEqual(short, "Short line.")
self.assertIn("Longer", long)
self.assertEqual(params, {})
self.assertIsNone(returns)


class TestMultiDocParserGoogleStyle(TestCase):
def test_google_style_params(self):
docstring = """Compute the sum.

Args:
x (int): First number.
y (int): Second number.

Returns:
int: The sum.
"""
short, long, params, returns = multi_doc_parser(docstring)
self.assertEqual(short, "Compute the sum.")
self.assertIn("x", params)
self.assertIn("y", params)
self.assertEqual(params["x"]["doc"], "First number.")
self.assertEqual(params["x"]["type"], "int")
self.assertEqual(params["y"]["doc"], "Second number.")
self.assertIsNotNone(returns)

def test_google_style_param_without_type(self):
docstring = """Do a thing.

Args:
name: A name string.
"""
_, _, params, _ = multi_doc_parser(docstring)
self.assertIn("name", params)
self.assertIsNone(params["name"]["type"])
self.assertEqual(params["name"]["doc"], "A name string.")

def test_google_style_no_returns(self):
docstring = """Prints something.

Args:
msg (str): The message.
"""
_, _, _, returns = multi_doc_parser(docstring)
self.assertIsNone(returns)


class TestMultiDocParserSphinxStyle(TestCase):
def test_sphinx_style_params(self):
docstring = """Fetch a resource.

:param str url: The URL to fetch.
:param int timeout: Seconds before timeout.
:returns: The response body.
"""
short, _, params, returns = multi_doc_parser(docstring)
self.assertEqual(short, "Fetch a resource.")
self.assertIn("url", params)
self.assertEqual(params["url"]["type"], "str")
self.assertIn("timeout", params)
self.assertEqual(params["timeout"]["type"], "int")
self.assertIsNotNone(returns)

def test_sphinx_style_type_via_type_directive(self):
docstring = """:param name: A name.
:type name: str
"""
_, _, params, _ = multi_doc_parser(docstring)
self.assertIn("name", params)
self.assertEqual(params["name"]["type"], "str")


class TestMultiDocParserNumpyStyle(TestCase):
def test_numpy_style_params(self):
docstring = """Add two numbers.

Parameters
----------
a : int
First operand.
b : int
Second operand.

Returns
-------
int
The result.
"""
short, _, params, returns = multi_doc_parser(docstring)
self.assertEqual(short, "Add two numbers.")
self.assertIn("a", params)
self.assertEqual(params["a"]["type"], "int")
self.assertIn("b", params)
self.assertIsNotNone(returns)


class TestMultiDocParserReturnValues(TestCase):
def test_returns_description_is_string(self):
docstring = """Get a value.

Returns:
str: The value string.
"""
_, _, _, returns = multi_doc_parser(docstring)
self.assertIsInstance(returns, str)
self.assertTrue(len(returns) > 0)

def test_no_return_section_yields_none(self):
docstring = """Just do stuff."""
_, _, _, returns = multi_doc_parser(docstring)
self.assertIsNone(returns)
Loading
Loading