Merge branch 'master' into michael-sayapin/master
This commit is contained in:
@@ -12,12 +12,12 @@ inputs/
|
||||
|
||||
## Test case directory structure
|
||||
|
||||
Each testcase has a `<name>.proto` file with a message called `Test`, a matching `.json` file and optionally a custom test file called `test_*.py`.
|
||||
Each testcase has a `<name>.proto` file with a message called `Test`, and optionally a matching `.json` file and a custom test called `test_*.py`.
|
||||
|
||||
```bash
|
||||
bool/
|
||||
bool.proto
|
||||
bool.json
|
||||
bool.json # optional
|
||||
test_bool.py # optional
|
||||
```
|
||||
|
||||
@@ -61,21 +61,22 @@ def test_value():
|
||||
|
||||
The following tests are automatically executed for all cases:
|
||||
|
||||
- [x] Can the generated python code imported?
|
||||
- [x] Can the generated python code be imported?
|
||||
- [x] Can the generated message class be instantiated?
|
||||
- [x] Is the generated code compatible with the Google's `grpc_tools.protoc` implementation?
|
||||
- _when `.json` is present_
|
||||
|
||||
## Running the tests
|
||||
|
||||
- `pipenv run generate`
|
||||
This generates
|
||||
- `pipenv run generate`
|
||||
This generates:
|
||||
- `betterproto/tests/output_betterproto` — *the plugin generated python classes*
|
||||
- `betterproto/tests/output_reference` — *reference implementation classes*
|
||||
- `pipenv run test`
|
||||
|
||||
## Intentionally Failing tests
|
||||
|
||||
The standard test suite includes tests that fail by intention. These tests document known bugs and missing features that are intended to be corrented in the future.
|
||||
The standard test suite includes tests that fail by intention. These tests document known bugs and missing features that are intended to be corrected in the future.
|
||||
|
||||
When running `pytest`, they show up as `x` or `X` in the test results.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from betterproto.tests.output_betterproto.bool.bool import Test
|
||||
from betterproto.tests.output_betterproto.bool import Test
|
||||
|
||||
|
||||
def test_value():
|
||||
|
||||
@@ -10,6 +10,7 @@ message Test {
|
||||
int32 camelCase = 1;
|
||||
my_enum snake_case = 2;
|
||||
snake_case_message snake_case_message = 3;
|
||||
int32 UPPERCASE = 4;
|
||||
}
|
||||
|
||||
message snake_case_message {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import betterproto.tests.output_betterproto.casing.casing as casing
|
||||
from betterproto.tests.output_betterproto.casing.casing import Test
|
||||
import betterproto.tests.output_betterproto.casing as casing
|
||||
from betterproto.tests.output_betterproto.casing import Test
|
||||
|
||||
|
||||
def test_message_attributes():
|
||||
@@ -8,6 +8,7 @@ def test_message_attributes():
|
||||
message, "snake_case_message"
|
||||
), "snake_case field name is same in python"
|
||||
assert hasattr(message, "camel_case"), "CamelCase field is snake_case in python"
|
||||
assert hasattr(message, "uppercase"), "UPPERCASE field is lowercase in python"
|
||||
|
||||
|
||||
def test_message_casing():
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"UPPERCASE": 10,
|
||||
"UPPERCASE_V2": 10,
|
||||
"UPPER_CAMEL_CASE": 10
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
from betterproto.tests.output_betterproto.casing_message_field_uppercase.casing_message_field_uppercase import (
|
||||
Test,
|
||||
)
|
||||
from betterproto.tests.output_betterproto.casing_message_field_uppercase import Test
|
||||
|
||||
|
||||
def test_message_casing():
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
# Test cases that are expected to fail, e.g. unimplemented features or bug-fixes.
|
||||
# Remove from list when fixed.
|
||||
tests = {
|
||||
"import_root_sibling", # 61
|
||||
"import_child_package_from_package", # 58
|
||||
"import_root_package_from_child", # 60
|
||||
"import_parent_package_from_child", # 59
|
||||
"import_circular_dependency", # failing because of other bugs now
|
||||
"import_packages_same_name", # 25
|
||||
xfail = {
|
||||
"import_circular_dependency",
|
||||
"oneof_enum", # 63
|
||||
"casing_message_field_uppercase", # 11
|
||||
"namespace_keywords", # 70
|
||||
"namespace_builtin_types", # 53
|
||||
"googletypes_struct", # 9
|
||||
"googletypes_value", # 9
|
||||
"enum_skipped_value", # 93
|
||||
"import_capitalized_package",
|
||||
"example", # This is the example in the readme. Not a test.
|
||||
}
|
||||
|
||||
services = {
|
||||
|
||||
8
betterproto/tests/inputs/example/example.proto
Normal file
8
betterproto/tests/inputs/example/example.proto
Normal file
@@ -0,0 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package hello;
|
||||
|
||||
// Greeting represents a message you can tell a user.
|
||||
message Greeting {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import betterproto.lib.google.protobuf as protobuf
|
||||
import pytest
|
||||
|
||||
from betterproto.tests.mocks import MockChannel
|
||||
from betterproto.tests.output_betterproto.googletypes_response.googletypes_response import (
|
||||
TestStub,
|
||||
)
|
||||
from betterproto.tests.output_betterproto.googletypes_response import TestStub
|
||||
|
||||
test_cases = [
|
||||
(TestStub.get_double, protobuf.DoubleValue, 2.5),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from betterproto.tests.mocks import MockChannel
|
||||
from betterproto.tests.output_betterproto.googletypes_response_embedded.googletypes_response_embedded import (
|
||||
from betterproto.tests.output_betterproto.googletypes_response_embedded import (
|
||||
Output,
|
||||
TestStub,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
|
||||
package Capitalized;
|
||||
|
||||
message Message {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "capitalized.proto";
|
||||
|
||||
// Tests that we can import from a package with a capital name, that looks like a nested type, but isn't.
|
||||
|
||||
message Test {
|
||||
Capitalized.Message message = 1;
|
||||
}
|
||||
@@ -3,9 +3,9 @@ syntax = "proto3";
|
||||
import "root.proto";
|
||||
import "other.proto";
|
||||
|
||||
// This test-case verifies that future implementations will support circular dependencies in the generated python files.
|
||||
// This test-case verifies support for circular dependencies in the generated python files.
|
||||
//
|
||||
// This becomes important when generating 1 python file/module per package, rather than 1 file per proto file.
|
||||
// This is important because we generate 1 python file/module per package, rather than 1 file per proto file.
|
||||
//
|
||||
// Scenario:
|
||||
//
|
||||
@@ -24,5 +24,5 @@ import "other.proto";
|
||||
// (root: Test & RootPackageMessage) <-------> (other: OtherPackageMessage)
|
||||
message Test {
|
||||
RootPackageMessage message = 1;
|
||||
other.OtherPackageMessage other =2;
|
||||
other.OtherPackageMessage other = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cousin.cousin_subpackage;
|
||||
|
||||
message CousinMessage {
|
||||
}
|
||||
11
betterproto/tests/inputs/import_cousin_package/test.proto
Normal file
11
betterproto/tests/inputs/import_cousin_package/test.proto
Normal file
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package test.subpackage;
|
||||
|
||||
import "cousin.proto";
|
||||
|
||||
// Verify that we can import message unrelated to us
|
||||
|
||||
message Test {
|
||||
cousin.cousin_subpackage.CousinMessage message = 1;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cousin.subpackage;
|
||||
|
||||
message CousinMessage {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package test.subpackage;
|
||||
|
||||
import "cousin.proto";
|
||||
|
||||
// Verify that we can import a message unrelated to us, in a subpackage with the same name as us.
|
||||
|
||||
message Test {
|
||||
cousin.subpackage.CousinMessage message = 1;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package child;
|
||||
|
||||
import "root.proto";
|
||||
|
||||
// Verify that we can import root message from child package
|
||||
|
||||
message Test {
|
||||
RootMessage message = 1;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "root.proto";
|
||||
|
||||
package child;
|
||||
|
||||
// Tests generated imports when a message inside a child-package refers to a message defined in the root.
|
||||
|
||||
message Test {
|
||||
RootMessage message = 1;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from betterproto.tests.mocks import MockChannel
|
||||
from betterproto.tests.output_betterproto.import_service_input_message.import_service_input_message import (
|
||||
from betterproto.tests.output_betterproto.import_service_input_message import (
|
||||
RequestResponse,
|
||||
TestStub,
|
||||
)
|
||||
|
||||
@@ -15,4 +15,4 @@ message Test {
|
||||
|
||||
message Sibling {
|
||||
int32 foo = 1;
|
||||
}
|
||||
}
|
||||
19
betterproto/tests/inputs/nested2/nested2.proto
Normal file
19
betterproto/tests/inputs/nested2/nested2.proto
Normal file
@@ -0,0 +1,19 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "package.proto";
|
||||
|
||||
message Game {
|
||||
message Player {
|
||||
enum Race {
|
||||
human = 0;
|
||||
orc = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message Test {
|
||||
Game game = 1;
|
||||
Game.Player GamePlayer = 2;
|
||||
Game.Player.Race GamePlayerRace = 3;
|
||||
equipment.Weapon Weapon = 4;
|
||||
}
|
||||
7
betterproto/tests/inputs/nested2/package.proto
Normal file
7
betterproto/tests/inputs/nested2/package.proto
Normal file
@@ -0,0 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package equipment;
|
||||
|
||||
message Weapon {
|
||||
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"root": {
|
||||
"top": {
|
||||
"name": "double-nested",
|
||||
"parent": {
|
||||
"child": [{"foo": "hello"}],
|
||||
"enumChild": ["A"],
|
||||
"rootParentChild": [{"a": "hello"}],
|
||||
"middle": {
|
||||
"bottom": [{"foo": "hello"}],
|
||||
"enumBottom": ["A"],
|
||||
"topMiddleBottom": [{"a": "hello"}],
|
||||
"bar": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message Test {
|
||||
message Root {
|
||||
message Parent {
|
||||
message RootParentChild {
|
||||
message Top {
|
||||
message Middle {
|
||||
message TopMiddleBottom {
|
||||
string a = 1;
|
||||
}
|
||||
enum EnumChild{
|
||||
enum EnumBottom{
|
||||
A = 0;
|
||||
B = 1;
|
||||
}
|
||||
message Child {
|
||||
message Bottom {
|
||||
string foo = 1;
|
||||
}
|
||||
reserved 1;
|
||||
repeated Child child = 2;
|
||||
repeated EnumChild enumChild=3;
|
||||
repeated RootParentChild rootParentChild=4;
|
||||
repeated Bottom bottom = 2;
|
||||
repeated EnumBottom enumBottom=3;
|
||||
repeated TopMiddleBottom topMiddleBottom=4;
|
||||
bool bar = 5;
|
||||
}
|
||||
string name = 1;
|
||||
Parent parent = 2;
|
||||
Middle middle = 2;
|
||||
}
|
||||
Root root = 1;
|
||||
Top top = 1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import betterproto
|
||||
from betterproto.tests.output_betterproto.oneof.oneof import Test
|
||||
from betterproto.tests.output_betterproto.oneof import Test
|
||||
from betterproto.tests.util import get_test_case_json_data
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
import betterproto
|
||||
from betterproto.tests.output_betterproto.oneof_enum.oneof_enum import (
|
||||
from betterproto.tests.output_betterproto.oneof_enum import (
|
||||
Move,
|
||||
Signal,
|
||||
Test,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package ref;
|
||||
|
||||
import "repeatedmessage.proto";
|
||||
|
||||
message Test {
|
||||
|
||||
125
betterproto/tests/test_casing.py
Normal file
125
betterproto/tests/test_casing.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import pytest
|
||||
|
||||
from betterproto.casing import camel_case, pascal_case, snake_case
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["value", "expected"],
|
||||
[
|
||||
("", ""),
|
||||
("a", "A"),
|
||||
("foobar", "Foobar"),
|
||||
("fooBar", "FooBar"),
|
||||
("FooBar", "FooBar"),
|
||||
("foo.bar", "FooBar"),
|
||||
("foo_bar", "FooBar"),
|
||||
("FOOBAR", "Foobar"),
|
||||
("FOOBar", "FooBar"),
|
||||
("UInt32", "UInt32"),
|
||||
("FOO_BAR", "FooBar"),
|
||||
("FOOBAR1", "Foobar1"),
|
||||
("FOOBAR_1", "Foobar1"),
|
||||
("FOO1BAR2", "Foo1Bar2"),
|
||||
("foo__bar", "FooBar"),
|
||||
("_foobar", "Foobar"),
|
||||
("foobaR", "FoobaR"),
|
||||
("foo~bar", "FooBar"),
|
||||
("foo:bar", "FooBar"),
|
||||
("1foobar", "1Foobar"),
|
||||
],
|
||||
)
|
||||
def test_pascal_case(value, expected):
|
||||
actual = pascal_case(value, strict=True)
|
||||
assert actual == expected, f"{value} => {expected} (actual: {actual})"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["value", "expected"],
|
||||
[
|
||||
("", ""),
|
||||
("a", "a"),
|
||||
("foobar", "foobar"),
|
||||
("fooBar", "fooBar"),
|
||||
("FooBar", "fooBar"),
|
||||
("foo.bar", "fooBar"),
|
||||
("foo_bar", "fooBar"),
|
||||
("FOOBAR", "foobar"),
|
||||
("FOO_BAR", "fooBar"),
|
||||
("FOOBAR1", "foobar1"),
|
||||
("FOOBAR_1", "foobar1"),
|
||||
("FOO1BAR2", "foo1Bar2"),
|
||||
("foo__bar", "fooBar"),
|
||||
("_foobar", "foobar"),
|
||||
("foobaR", "foobaR"),
|
||||
("foo~bar", "fooBar"),
|
||||
("foo:bar", "fooBar"),
|
||||
("1foobar", "1Foobar"),
|
||||
],
|
||||
)
|
||||
def test_camel_case_strict(value, expected):
|
||||
actual = camel_case(value, strict=True)
|
||||
assert actual == expected, f"{value} => {expected} (actual: {actual})"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["value", "expected"],
|
||||
[
|
||||
("foo_bar", "fooBar"),
|
||||
("FooBar", "fooBar"),
|
||||
("foo__bar", "foo_Bar"),
|
||||
("foo__Bar", "foo__Bar"),
|
||||
],
|
||||
)
|
||||
def test_camel_case_not_strict(value, expected):
|
||||
actual = camel_case(value, strict=False)
|
||||
assert actual == expected, f"{value} => {expected} (actual: {actual})"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["value", "expected"],
|
||||
[
|
||||
("", ""),
|
||||
("a", "a"),
|
||||
("foobar", "foobar"),
|
||||
("fooBar", "foo_bar"),
|
||||
("FooBar", "foo_bar"),
|
||||
("foo.bar", "foo_bar"),
|
||||
("foo_bar", "foo_bar"),
|
||||
("foo_Bar", "foo_bar"),
|
||||
("FOOBAR", "foobar"),
|
||||
("FOOBar", "foo_bar"),
|
||||
("UInt32", "u_int32"),
|
||||
("FOO_BAR", "foo_bar"),
|
||||
("FOOBAR1", "foobar1"),
|
||||
("FOOBAR_1", "foobar_1"),
|
||||
("FOOBAR_123", "foobar_123"),
|
||||
("FOO1BAR2", "foo1_bar2"),
|
||||
("foo__bar", "foo_bar"),
|
||||
("_foobar", "foobar"),
|
||||
("foobaR", "fooba_r"),
|
||||
("foo~bar", "foo_bar"),
|
||||
("foo:bar", "foo_bar"),
|
||||
("1foobar", "1_foobar"),
|
||||
("GetUInt64", "get_u_int64"),
|
||||
],
|
||||
)
|
||||
def test_snake_case_strict(value, expected):
|
||||
actual = snake_case(value)
|
||||
assert actual == expected, f"{value} => {expected} (actual: {actual})"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["value", "expected"],
|
||||
[
|
||||
("fooBar", "foo_bar"),
|
||||
("FooBar", "foo_bar"),
|
||||
("foo_Bar", "foo__bar"),
|
||||
("foo__bar", "foo__bar"),
|
||||
("FOOBar", "foo_bar"),
|
||||
("__foo", "__foo"),
|
||||
("GetUInt64", "get_u_int64"),
|
||||
],
|
||||
)
|
||||
def test_snake_case_not_strict(value, expected):
|
||||
actual = snake_case(value, strict=False)
|
||||
assert actual == expected, f"{value} => {expected} (actual: {actual})"
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from ..compile.importing import get_ref_type
|
||||
from ..compile.importing import get_type_reference, parse_source_type_name
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -28,14 +28,16 @@ from ..compile.importing import get_ref_type
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_import_google_wellknown_types_non_wrappers(
|
||||
def test_reference_google_wellknown_types_non_wrappers(
|
||||
google_type: str, expected_name: str, expected_import: str
|
||||
):
|
||||
imports = set()
|
||||
name = get_ref_type(package="", imports=imports, type_name=google_type)
|
||||
name = get_type_reference(package="", imports=imports, source_type=google_type)
|
||||
|
||||
assert name == expected_name
|
||||
assert imports.__contains__(expected_import)
|
||||
assert imports.__contains__(
|
||||
expected_import
|
||||
), f"{expected_import} not found in {imports}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -52,9 +54,11 @@ def test_import_google_wellknown_types_non_wrappers(
|
||||
(".google.protobuf.BytesValue", "Optional[bytes]"),
|
||||
],
|
||||
)
|
||||
def test_importing_google_wrappers_unwraps_them(google_type: str, expected_name: str):
|
||||
def test_referenceing_google_wrappers_unwraps_them(
|
||||
google_type: str, expected_name: str
|
||||
):
|
||||
imports = set()
|
||||
name = get_ref_type(package="", imports=imports, type_name=google_type)
|
||||
name = get_type_reference(package="", imports=imports, source_type=google_type)
|
||||
|
||||
assert name == expected_name
|
||||
assert imports == set()
|
||||
@@ -74,9 +78,238 @@ def test_importing_google_wrappers_unwraps_them(google_type: str, expected_name:
|
||||
(".google.protobuf.BytesValue", "betterproto_lib_google_protobuf.BytesValue"),
|
||||
],
|
||||
)
|
||||
def test_importing_google_wrappers_without_unwrapping(
|
||||
def test_referenceing_google_wrappers_without_unwrapping(
|
||||
google_type: str, expected_name: str
|
||||
):
|
||||
name = get_ref_type(package="", imports=set(), type_name=google_type, unwrap=False)
|
||||
name = get_type_reference(
|
||||
package="", imports=set(), source_type=google_type, unwrap=False
|
||||
)
|
||||
|
||||
assert name == expected_name
|
||||
|
||||
|
||||
def test_reference_child_package_from_package():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package", imports=imports, source_type="package.child.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from . import child"}
|
||||
assert name == "child.Message"
|
||||
|
||||
|
||||
def test_reference_child_package_from_root():
|
||||
imports = set()
|
||||
name = get_type_reference(package="", imports=imports, source_type="child.Message")
|
||||
|
||||
assert imports == {"from . import child"}
|
||||
assert name == "child.Message"
|
||||
|
||||
|
||||
def test_reference_camel_cased():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="", imports=imports, source_type="child_package.example_message"
|
||||
)
|
||||
|
||||
assert imports == {"from . import child_package"}
|
||||
assert name == "child_package.ExampleMessage"
|
||||
|
||||
|
||||
def test_reference_nested_child_from_root():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="", imports=imports, source_type="nested.child.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from .nested import child as nested_child"}
|
||||
assert name == "nested_child.Message"
|
||||
|
||||
|
||||
def test_reference_deeply_nested_child_from_root():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="", imports=imports, source_type="deeply.nested.child.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from .deeply.nested import child as deeply_nested_child"}
|
||||
assert name == "deeply_nested_child.Message"
|
||||
|
||||
|
||||
def test_reference_deeply_nested_child_from_package():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package",
|
||||
imports=imports,
|
||||
source_type="package.deeply.nested.child.Message",
|
||||
)
|
||||
|
||||
assert imports == {"from .deeply.nested import child as deeply_nested_child"}
|
||||
assert name == "deeply_nested_child.Message"
|
||||
|
||||
|
||||
def test_reference_root_sibling():
|
||||
imports = set()
|
||||
name = get_type_reference(package="", imports=imports, source_type="Message")
|
||||
|
||||
assert imports == set()
|
||||
assert name == '"Message"'
|
||||
|
||||
|
||||
def test_reference_nested_siblings():
|
||||
imports = set()
|
||||
name = get_type_reference(package="foo", imports=imports, source_type="foo.Message")
|
||||
|
||||
assert imports == set()
|
||||
assert name == '"Message"'
|
||||
|
||||
|
||||
def test_reference_deeply_nested_siblings():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="foo.bar", imports=imports, source_type="foo.bar.Message"
|
||||
)
|
||||
|
||||
assert imports == set()
|
||||
assert name == '"Message"'
|
||||
|
||||
|
||||
def test_reference_parent_package_from_child():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package.child", imports=imports, source_type="package.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ... import package as __package__"}
|
||||
assert name == "__package__.Message"
|
||||
|
||||
|
||||
def test_reference_parent_package_from_deeply_nested_child():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package.deeply.nested.child",
|
||||
imports=imports,
|
||||
source_type="package.deeply.nested.Message",
|
||||
)
|
||||
|
||||
assert imports == {"from ... import nested as __nested__"}
|
||||
assert name == "__nested__.Message"
|
||||
|
||||
|
||||
def test_reference_ancestor_package_from_nested_child():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package.ancestor.nested.child",
|
||||
imports=imports,
|
||||
source_type="package.ancestor.Message",
|
||||
)
|
||||
|
||||
assert imports == {"from .... import ancestor as ___ancestor__"}
|
||||
assert name == "___ancestor__.Message"
|
||||
|
||||
|
||||
def test_reference_root_package_from_child():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package.child", imports=imports, source_type="Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ... import Message as __Message__"}
|
||||
assert name == "__Message__"
|
||||
|
||||
|
||||
def test_reference_root_package_from_deeply_nested_child():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="package.deeply.nested.child", imports=imports, source_type="Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ..... import Message as ____Message__"}
|
||||
assert name == "____Message__"
|
||||
|
||||
|
||||
def test_reference_unrelated_package():
|
||||
imports = set()
|
||||
name = get_type_reference(package="a", imports=imports, source_type="p.Message")
|
||||
|
||||
assert imports == {"from .. import p as _p__"}
|
||||
assert name == "_p__.Message"
|
||||
|
||||
|
||||
def test_reference_unrelated_nested_package():
|
||||
imports = set()
|
||||
name = get_type_reference(package="a.b", imports=imports, source_type="p.q.Message")
|
||||
|
||||
assert imports == {"from ...p import q as __p_q__"}
|
||||
assert name == "__p_q__.Message"
|
||||
|
||||
|
||||
def test_reference_unrelated_deeply_nested_package():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="a.b.c.d", imports=imports, source_type="p.q.r.s.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from .....p.q.r import s as ____p_q_r_s__"}
|
||||
assert name == "____p_q_r_s__.Message"
|
||||
|
||||
|
||||
def test_reference_cousin_package():
|
||||
imports = set()
|
||||
name = get_type_reference(package="a.x", imports=imports, source_type="a.y.Message")
|
||||
|
||||
assert imports == {"from .. import y as _y__"}
|
||||
assert name == "_y__.Message"
|
||||
|
||||
|
||||
def test_reference_cousin_package_different_name():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="test.package1", imports=imports, source_type="cousin.package2.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ...cousin import package2 as __cousin_package2__"}
|
||||
assert name == "__cousin_package2__.Message"
|
||||
|
||||
|
||||
def test_reference_cousin_package_same_name():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="test.package", imports=imports, source_type="cousin.package.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ...cousin import package as __cousin_package__"}
|
||||
assert name == "__cousin_package__.Message"
|
||||
|
||||
|
||||
def test_reference_far_cousin_package():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="a.x.y", imports=imports, source_type="a.b.c.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ...b import c as __b_c__"}
|
||||
assert name == "__b_c__.Message"
|
||||
|
||||
|
||||
def test_reference_far_far_cousin_package():
|
||||
imports = set()
|
||||
name = get_type_reference(
|
||||
package="a.x.y.z", imports=imports, source_type="a.b.c.d.Message"
|
||||
)
|
||||
|
||||
assert imports == {"from ....b.c import d as ___b_c_d__"}
|
||||
assert name == "___b_c_d__.Message"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["full_name", "expected_output"],
|
||||
[
|
||||
("package.SomeMessage.NestedType", ("package", "SomeMessage.NestedType")),
|
||||
(".package.SomeMessage.NestedType", ("package", "SomeMessage.NestedType")),
|
||||
(".service.ExampleRequest", ("service", "ExampleRequest")),
|
||||
(".package.lower_case_message", ("package", "lower_case_message")),
|
||||
],
|
||||
)
|
||||
def test_parse_field_type_name(full_name, expected_output):
|
||||
assert parse_source_type_name(full_name) == expected_output
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from types import ModuleType
|
||||
from typing import Set
|
||||
|
||||
import pytest
|
||||
@@ -10,7 +11,12 @@ import pytest
|
||||
import betterproto
|
||||
from betterproto.tests.inputs import config as test_input_config
|
||||
from betterproto.tests.mocks import MockChannel
|
||||
from betterproto.tests.util import get_directories, get_test_case_json_data, inputs_path
|
||||
from betterproto.tests.util import (
|
||||
find_module,
|
||||
get_directories,
|
||||
get_test_case_json_data,
|
||||
inputs_path,
|
||||
)
|
||||
|
||||
# Force pure-python implementation instead of C++, otherwise imports
|
||||
# break things because we can't properly reset the symbol database.
|
||||
@@ -50,14 +56,17 @@ class TestCases:
|
||||
test_cases = TestCases(
|
||||
path=inputs_path,
|
||||
services=test_input_config.services,
|
||||
xfail=test_input_config.tests,
|
||||
xfail=test_input_config.xfail,
|
||||
)
|
||||
|
||||
plugin_output_package = "betterproto.tests.output_betterproto"
|
||||
reference_output_package = "betterproto.tests.output_reference"
|
||||
|
||||
TestData = namedtuple("TestData", ["plugin_module", "reference_module", "json_data"])
|
||||
|
||||
TestData = namedtuple("TestData", "plugin_module, reference_module, json_data")
|
||||
|
||||
def module_has_entry_point(module: ModuleType):
|
||||
return any(hasattr(module, attr) for attr in ["Test", "TestStub"])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -75,11 +84,19 @@ def test_data(request):
|
||||
|
||||
sys.path.append(reference_module_root)
|
||||
|
||||
plugin_module = importlib.import_module(f"{plugin_output_package}.{test_case_name}")
|
||||
|
||||
plugin_module_entry_point = find_module(plugin_module, module_has_entry_point)
|
||||
|
||||
if not plugin_module_entry_point:
|
||||
raise Exception(
|
||||
f"Test case {repr(test_case_name)} has no entry point. "
|
||||
"Please add a proto message or service called Test and recompile."
|
||||
)
|
||||
|
||||
yield (
|
||||
TestData(
|
||||
plugin_module=importlib.import_module(
|
||||
f"{plugin_output_package}.{test_case_name}.{test_case_name}"
|
||||
),
|
||||
plugin_module=plugin_module_entry_point,
|
||||
reference_module=lambda: importlib.import_module(
|
||||
f"{reference_output_package}.{test_case_name}.{test_case_name}_pb2"
|
||||
),
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
from typing import Generator, IO, Optional
|
||||
from types import ModuleType
|
||||
from typing import Callable, Generator, Optional
|
||||
|
||||
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
||||
|
||||
@@ -55,3 +58,35 @@ def get_test_case_json_data(test_case_name: str, json_file_name: Optional[str] =
|
||||
|
||||
with test_data_file_path.open("r") as fh:
|
||||
return fh.read()
|
||||
|
||||
|
||||
def find_module(
|
||||
module: ModuleType, predicate: Callable[[ModuleType], bool]
|
||||
) -> Optional[ModuleType]:
|
||||
"""
|
||||
Recursively search module tree for a module that matches the search predicate.
|
||||
Assumes that the submodules are directories containing __init__.py.
|
||||
|
||||
Example:
|
||||
|
||||
# find module inside foo that contains Test
|
||||
import foo
|
||||
test_module = find_module(foo, lambda m: hasattr(m, 'Test'))
|
||||
"""
|
||||
if predicate(module):
|
||||
return module
|
||||
|
||||
module_path = pathlib.Path(*module.__path__)
|
||||
|
||||
for sub in list(sub.parent for sub in module_path.glob("**/__init__.py")):
|
||||
if sub == module_path:
|
||||
continue
|
||||
sub_module_path = sub.relative_to(module_path)
|
||||
sub_module_name = ".".join(sub_module_path.parts)
|
||||
|
||||
sub_module = importlib.import_module(f".{sub_module_name}", module.__name__)
|
||||
|
||||
if predicate(sub_module):
|
||||
return sub_module
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user