Skip to content

Commit

Permalink
Add unit test (remove unnecessary test class) and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kinow committed Nov 22, 2023
1 parent 5fe3b07 commit e0c9d2e
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 104 deletions.
Binary file added .github/docs/simpler_names_issue_10_pb2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ Running the command above should create a final image with your diagram as seen

![](./.github/docs/issue_10_pb2.png)

Note that by default it uses the full name of the types (e.g. `SomeRequest.shipments`).
If you would like to use simpler names (i.e. `shipments`) you can use the option added
in 0.13 `--full_names=true|false` (it is `true` by default for backward compatibility).

```bash
$ PYTHONPATH=. protobuf-uml-diagram --proto issue_10_pb2 --output /tmp --full_names=false
INFO:protobuf_uml_diagram:Imported: issue_10_pb2
INFO:protobuf_uml_diagram:Writing diagram to /tmp/issue_10_pb2.png
$ eog /tmp/issue_10_pb2.png
```

> Note that doing so, you risk showing fields that are homonyms but that mean different
> things. See the related issues [#10](https://github.com/kinow/protobuf-uml-diagram/issues/10)
> and [#78](https://github.com/kinow/protobuf-uml-diagram/issues/78).
![](./.github/docs/simpler_names_issue_10_pb2.png)

## Installation

```bash
Expand Down
5 changes: 2 additions & 3 deletions protobuf_uml_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ def _get_field_name(descriptor: Descriptor, full_names=True) -> str:
return descriptor.name



def _process_descriptor(
descriptor: Descriptor, classes: List[str],
relationships: List[str],
Expand All @@ -103,7 +102,6 @@ def _process_descriptor(
"""
# Here users are able to choose between ClassName.type_name (full name included) or just type_name


type_template_text = StringIO()
this_node = _get_field_name(descriptor, full_names=full_names)
type_template_text.write(
Expand Down Expand Up @@ -256,7 +254,8 @@ def build(self):
help='Compiled Python proto module (e.g. some.package.ws_compiled_pb2).')
@click.option('--output', type=PathPath(file_okay=False), required=True,
help='Output directory.')
@click.option('--full_names', type=bool, required=False, default=True, help='Use full names (Class.type) or not (type) in diagram.')
@click.option('--full_names', type=bool, required=False, default=True,
help='Use full names (Class.type) or not (type) in diagram.')
def main(proto: str, output: Path, full_names: bool) -> None:
Diagram() \
.from_file(proto) \
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def read(*parts):
'codecov==2.1.*',
'coverage>=5.3,<7.4',
'pytest-cov>=2.10,<4.2',
'pytest-env>=0.6,<1.2',
'pytest-env==0.6,<1.2',
'pytest-mock>=3.11,<3.13',
'pytest>=6.1,<7.5',
'pycodestyle>=2.6,<2.12'
]
Expand Down
221 changes: 121 additions & 100 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,115 +15,136 @@
import os
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import ANY

import pytest

import protobuf_uml_diagram
from protobuf_uml_diagram import PathPath, Diagram


def test_path_path():
"""Test the converter used for the command line args."""
path_path = PathPath()
path = path_path.convert(value="blue", param="color", ctx=None)
path = path_path.convert(value="blue", param="color", ctx=None) # type: ignore
assert isinstance(path, Path)


class TestDiagramBuilder:

def test_from_file_raises(self):
with pytest.raises(ValueError) as e:
Diagram().from_file('')
assert 'Missing proto file' in str(e.value)

def test_to_file_raises(self):
with pytest.raises(ValueError) as e:
Diagram().to_file(None) # type: ignore
assert 'Missing output location' in str(e.value)

def test_with_format_raises(self):
with pytest.raises(ValueError) as e:
Diagram().with_format(None) # type: ignore
assert 'Missing file' in str(e.value)

def test_build_raises(self):
with pytest.raises(ValueError) as e:
Diagram().build()
assert 'No Protobuf' in str(e.value)

with pytest.raises(ValueError) as e:
Diagram() \
.from_file('test_data.data_messages.proto') \
.build()
assert 'No output' in str(e.value)

with pytest.raises(ValueError) as e:
d = Diagram() \
.from_file('test_data.data_messages.proto') \
.to_file(Path('abc'))
d._file_format = None
d.build()
assert 'No file format' in str(e.value)

def test_happy_path(self):
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.data_messages.proto') \
.to_file(Path(tf)) \
.with_format('png') \
.build()
assert os.path.getsize(tf) > 0

def test_homonymous(self):
"""A test for when you have two 'subclasses' with same names."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.issue_10.proto') \
.to_file(Path(tf)) \
.with_format('png') \
.build()
assert os.path.getsize(tf) > 0

def test_logs_module_not_found(self):
with pytest.raises(ModuleNotFoundError) as e:
Diagram() \
.from_file('piracicaba') \
.build()
assert 'piracicaba' in str(e)

def test_contains_dot_proto_in_middle_of_the_name(self):
"""A test where the input data contains .proto, but doesn't end with it."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.issue_27.proto.configs_data_pb2') \
.to_file(Path(tf)) \
.with_format('png') \
.build()
assert os.path.getsize(tf) > 0


def test_to_file_with_missing_protobuf(self):
"""A test where the protobuf module is missing."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
d = Diagram().from_file('test_data.issue_27.proto.configs_data_pb2')
d._proto_module = None
with pytest.raises(ValueError) as cm:
d.to_file(Path(tf))
assert str(cm.value) == 'Missing protobuf module!'


def test_to_file_with_protobuf_missing_file(self):
"""A test where the protobuf module is present but invalid (no file)."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
d = Diagram().from_file('test_data.issue_27.proto.configs_data_pb2')
d._proto_module = {}
with pytest.raises(ValueError) as cm:
d.to_file(Path(tf))
assert str(cm.value) == 'Missing protobuf module!'



def test_from_file_raises():
with pytest.raises(ValueError) as e:
Diagram().from_file('')
assert 'Missing proto file' in str(e.value)


def test_to_file_raises():
with pytest.raises(ValueError) as e:
Diagram().to_file(None) # type: ignore
assert 'Missing output location' in str(e.value)


def test_with_format_raises():
with pytest.raises(ValueError) as e:
Diagram().with_format(None) # type: ignore
assert 'Missing file' in str(e.value)


def test_build_raises():
with pytest.raises(ValueError) as e:
Diagram().build()
assert 'No Protobuf' in str(e.value)

with pytest.raises(ValueError) as e:
Diagram() \
.from_file('test_data.data_messages.proto') \
.build()
assert 'No output' in str(e.value)

with pytest.raises(ValueError) as e:
d = Diagram() \
.from_file('test_data.data_messages.proto') \
.to_file(Path('abc'))
d._file_format = None
d.build()
assert 'No file format' in str(e.value)


def test_happy_path():
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.data_messages.proto') \
.to_file(Path(tf)) \
.with_format('png') \
.build()
assert os.path.getsize(tf) > 0


def test_homonymous(mocker):
"""A test for when you have two 'subclasses' with same names."""
spy = mocker.spy(protobuf_uml_diagram, '_get_field_name')
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.issue_10.proto') \
.to_file(Path(tf)) \
.with_format('png') \
.build()
assert os.path.getsize(tf) > 0
spy.assert_called_with(descriptor=ANY, full_names=True)


def test_homonymous_user_does_not_care(mocker):
"""A test for when you have two 'subclasses' with same names, but you do not care."""
spy = mocker.spy(protobuf_uml_diagram, '_get_field_name')
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.issue_10.proto') \
.to_file(Path(tf)) \
.with_format('png') \
.with_full_names(False) \
.build()
assert os.path.getsize(tf) > 0
spy.assert_called_with(descriptor=ANY, full_names=False)


def test_logs_module_not_found():
with pytest.raises(ModuleNotFoundError) as e:
Diagram() \
.from_file('piracicaba') \
.build()
assert 'piracicaba' in str(e)


def test_contains_dot_proto_in_middle_of_the_name():
"""A test where the input data contains .proto, but doesn't end with it."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
Diagram() \
.from_file('test_data.issue_27.proto.configs_data_pb2') \
.to_file(Path(tf)) \
.with_format('png') \
.build()
assert os.path.getsize(tf) > 0


def test_to_file_with_missing_protobuf():
"""A test where the protobuf module is missing."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
d = Diagram().from_file('test_data.issue_27.proto.configs_data_pb2')
d._proto_module = None
with pytest.raises(ValueError) as cm:
d.to_file(Path(tf))
assert str(cm.value) == 'Missing protobuf module!'


def test_to_file_with_protobuf_missing_file():
"""A test where the protobuf module is present but invalid (no file)."""
with TemporaryDirectory() as tmpdir:
tf = os.path.join(tmpdir, 'diagram.png')
d = Diagram().from_file('test_data.issue_27.proto.configs_data_pb2')
d._proto_module = {}
with pytest.raises(ValueError) as cm:
d.to_file(Path(tf))
assert str(cm.value) == 'Missing protobuf module!'

0 comments on commit e0c9d2e

Please sign in to comment.