Organize test-cases into folders, extract compatibility test into proper test, support adding test-case specific tests
This commit is contained in:
2
betterproto/plugin.bat
Normal file
2
betterproto/plugin.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@SET plugin_dir=%~dp0
|
||||
@python %plugin_dir%/plugin.py %*
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
|
||||
# Force pure-python implementation instead of C++, otherwise imports
|
||||
# break things because we can't properly reset the symbol database.
|
||||
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
||||
|
||||
import importlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Generator, Tuple
|
||||
|
||||
from google.protobuf import symbol_database
|
||||
from google.protobuf.descriptor_pool import DescriptorPool
|
||||
from google.protobuf.json_format import MessageToJson, Parse
|
||||
|
||||
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def get_files(end: str) -> Generator[str, None, None]:
|
||||
for r, dirs, files in os.walk(root):
|
||||
for filename in [f for f in files if f.endswith(end)]:
|
||||
yield os.path.join(r, filename)
|
||||
|
||||
|
||||
def get_base(filename: str) -> str:
|
||||
return os.path.splitext(os.path.basename(filename))[0]
|
||||
|
||||
|
||||
def ensure_ext(filename: str, ext: str) -> str:
|
||||
if not filename.endswith(ext):
|
||||
return filename + ext
|
||||
return filename
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(root)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
proto_files = [ensure_ext(f, ".proto") for f in sys.argv[1:]]
|
||||
bases = {get_base(f) for f in proto_files}
|
||||
json_files = [
|
||||
f for f in get_files(".json") if get_base(f).split("-")[0] in bases
|
||||
]
|
||||
else:
|
||||
proto_files = get_files(".proto")
|
||||
json_files = get_files(".json")
|
||||
|
||||
for filename in proto_files:
|
||||
print(f"Generating code for {os.path.basename(filename)}")
|
||||
subprocess.run(
|
||||
f"protoc --python_out=. {os.path.basename(filename)}", shell=True
|
||||
)
|
||||
subprocess.run(
|
||||
f"protoc --plugin=protoc-gen-custom=../plugin.py --custom_out=. {os.path.basename(filename)}",
|
||||
shell=True,
|
||||
)
|
||||
|
||||
for filename in json_files:
|
||||
# Reset the internal symbol database so we can import the `Test` message
|
||||
# multiple times. Ugh.
|
||||
sym = symbol_database.Default()
|
||||
sym.pool = DescriptorPool()
|
||||
|
||||
parts = get_base(filename).split("-")
|
||||
out = filename.replace(".json", ".bin")
|
||||
print(f"Using {parts[0]}_pb2 to generate {os.path.basename(out)}")
|
||||
|
||||
imported = importlib.import_module(f"{parts[0]}_pb2")
|
||||
input_json = open(filename).read()
|
||||
parsed = Parse(input_json, imported.Test())
|
||||
serialized = parsed.SerializeToString()
|
||||
preserve = "casing" not in filename
|
||||
serialized_json = MessageToJson(parsed, preserving_proto_field_name=preserve)
|
||||
|
||||
s_loaded = json.loads(serialized_json)
|
||||
in_loaded = json.loads(input_json)
|
||||
|
||||
if s_loaded != in_loaded:
|
||||
raise AssertionError("Expected JSON to be equal:", s_loaded, in_loaded)
|
||||
|
||||
open(out, "wb").write(serialized)
|
||||
8
betterproto/tests/inputs/bool/test.py
Normal file
8
betterproto/tests/inputs/bool/test.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from betterproto.tests.output_betterproto.bool.bool import Test
|
||||
from betterproto.tests.util import read_relative
|
||||
|
||||
|
||||
def test_value():
|
||||
message = Test().from_json(read_relative(__file__, 'bool.json'))
|
||||
assert message.value
|
||||
|
||||
@@ -9,4 +9,9 @@ enum my_enum {
|
||||
message Test {
|
||||
int32 camelCase = 1;
|
||||
my_enum snake_case = 2;
|
||||
snake_case_message snake_case_message = 3;
|
||||
}
|
||||
|
||||
message snake_case_message {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/wrappers.proto";
|
||||
|
||||
service Test {
|
||||
rpc GetInt32 (Input) returns (google.protobuf.Int32Value);
|
||||
rpc GetAnotherInt32 (Input) returns (google.protobuf.Int32Value);
|
||||
rpc GetInt64 (Input) returns (google.protobuf.Int64Value);
|
||||
rpc GetOutput (Input) returns (Output);
|
||||
}
|
||||
|
||||
message Input {
|
||||
|
||||
}
|
||||
|
||||
message Output {
|
||||
google.protobuf.Int64Value int64 = 1;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from betterproto.tests.output_betterproto.googletypes_response.googletypes_response import TestStub
|
||||
|
||||
|
||||
class TestStubChild(TestStub):
|
||||
async def _unary_unary(self, route, request, response_type, **kwargs):
|
||||
self.response_type = response_type
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test():
|
||||
pytest.skip('todo')
|
||||
stub = TestStubChild(None)
|
||||
await stub.get_int64()
|
||||
assert stub.response_type != Optional[int]
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package repeatedmessage;
|
||||
|
||||
message Test {
|
||||
repeated Sub greetings = 1;
|
||||
}
|
||||
|
||||
message Sub {
|
||||
string greeting = 1;
|
||||
}
|
||||
@@ -1,32 +1,99 @@
|
||||
import importlib
|
||||
import json
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
from .generate import get_base, get_files
|
||||
# Force pure-python implementation instead of C++, otherwise imports
|
||||
# break things because we can't properly reset the symbol database.
|
||||
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
||||
|
||||
inputs = get_files(".bin")
|
||||
from google.protobuf import symbol_database
|
||||
from google.protobuf.descriptor_pool import DescriptorPool
|
||||
from google.protobuf.json_format import Parse
|
||||
|
||||
import betterproto
|
||||
from betterproto.tests.util import get_directories, inputs_path
|
||||
|
||||
excluded_test_cases = {'googletypes_response', 'service'}
|
||||
test_case_names = {*get_directories(inputs_path)} - excluded_test_cases
|
||||
|
||||
plugin_output_package = 'betterproto.tests.output_betterproto'
|
||||
reference_output_package = 'betterproto.tests.output_reference'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename", inputs)
|
||||
def test_sample(filename: str) -> None:
|
||||
module = get_base(filename).split("-")[0]
|
||||
imported = importlib.import_module(f"betterproto.tests.{module}")
|
||||
data_binary = open(filename, "rb").read()
|
||||
data_dict = json.loads(open(filename.replace(".bin", ".json")).read())
|
||||
t1 = imported.Test().parse(data_binary)
|
||||
t2 = imported.Test().from_dict(data_dict)
|
||||
print(t1)
|
||||
print(t2)
|
||||
@pytest.mark.parametrize("test_case_name", test_case_names)
|
||||
def test_message_can_be_imported(test_case_name: str) -> None:
|
||||
importlib.import_module(f"{plugin_output_package}.{test_case_name}.{test_case_name}")
|
||||
|
||||
# Equality should automagically work for dataclasses!
|
||||
assert t1 == t2
|
||||
|
||||
# Generally this can't be relied on, but here we are aiming to match the
|
||||
# existing Python implementation and aren't doing anything tricky.
|
||||
# https://developers.google.com/protocol-buffers/docs/encoding#implications
|
||||
assert bytes(t1) == data_binary
|
||||
assert bytes(t2) == data_binary
|
||||
@pytest.mark.parametrize("test_case_name", test_case_names)
|
||||
def test_message_can_instantiated(test_case_name: str) -> None:
|
||||
plugin_module = importlib.import_module(f"{plugin_output_package}.{test_case_name}.{test_case_name}")
|
||||
plugin_module.Test()
|
||||
|
||||
assert t1.to_dict() == data_dict
|
||||
assert t2.to_dict() == data_dict
|
||||
|
||||
@pytest.mark.parametrize("test_case_name", test_case_names)
|
||||
def test_message_equality(test_case_name: str) -> None:
|
||||
plugin_module = importlib.import_module(f"{plugin_output_package}.{test_case_name}.{test_case_name}")
|
||||
message1 = plugin_module.Test()
|
||||
message2 = plugin_module.Test()
|
||||
assert message1 == message2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_case_name", test_case_names)
|
||||
def test_message_json(test_case_name: str) -> None:
|
||||
plugin_module = importlib.import_module(f"{plugin_output_package}.{test_case_name}.{test_case_name}")
|
||||
message: betterproto.Message = plugin_module.Test()
|
||||
reference_json_data = get_test_case_json_data(test_case_name)
|
||||
|
||||
message.from_json(reference_json_data)
|
||||
message_json = message.to_json(0)
|
||||
|
||||
assert json.loads(message_json) == json.loads(reference_json_data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_case_name", test_case_names)
|
||||
def test_binary_compatibility(test_case_name: str) -> None:
|
||||
# Reset the internal symbol database so we can import the `Test` message
|
||||
# multiple times. Ugh.
|
||||
sym = symbol_database.Default()
|
||||
sym.pool = DescriptorPool()
|
||||
|
||||
reference_module_root = os.path.join(*reference_output_package.split('.'), test_case_name)
|
||||
|
||||
print(f'Appending {reference_module_root}')
|
||||
sys.path.append(reference_module_root)
|
||||
|
||||
# import reference message
|
||||
reference_module = importlib.import_module(f"{reference_output_package}.{test_case_name}.{test_case_name}_pb2")
|
||||
plugin_module = importlib.import_module(f"{plugin_output_package}.{test_case_name}.{test_case_name}")
|
||||
|
||||
test_data = get_test_case_json_data(test_case_name)
|
||||
|
||||
reference_instance = Parse(test_data, reference_module.Test())
|
||||
reference_binary_output = reference_instance.SerializeToString()
|
||||
|
||||
plugin_instance_from_json: betterproto.Message = plugin_module.Test().from_json(test_data)
|
||||
plugin_instance_from_binary = plugin_module.Test.FromString(reference_binary_output)
|
||||
|
||||
# # Generally this can't be relied on, but here we are aiming to match the
|
||||
# # existing Python implementation and aren't doing anything tricky.
|
||||
# # https://developers.google.com/protocol-buffers/docs/encoding#implications
|
||||
assert plugin_instance_from_json == plugin_instance_from_binary
|
||||
assert plugin_instance_from_json.to_dict() == plugin_instance_from_binary.to_dict()
|
||||
|
||||
sys.path.remove(reference_module_root)
|
||||
|
||||
|
||||
'''
|
||||
helper methods
|
||||
'''
|
||||
|
||||
def get_test_case_json_data(test_case_name):
|
||||
test_data_path = os.path.join(inputs_path, test_case_name, f'{test_case_name}.json')
|
||||
if not os.path.exists(test_data_path):
|
||||
return None
|
||||
|
||||
with open(test_data_path) as fh:
|
||||
return fh.read()
|
||||
|
||||
@@ -3,8 +3,8 @@ import grpclib
|
||||
from grpclib.testing import ChannelFor
|
||||
import pytest
|
||||
from typing import Dict
|
||||
from .service import DoThingResponse, DoThingRequest, ExampleServiceStub
|
||||
|
||||
from betterproto.tests.output_betterproto.service.service import DoThingResponse, DoThingRequest, ExampleServiceStub
|
||||
|
||||
class ExampleService:
|
||||
def __init__(self, test_hook=None):
|
||||
|
||||
Reference in New Issue
Block a user