diff --git a/.flake8 b/.flake8 index 0081794..dfc6ca4 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] ignore = E203,W503 +exclude = tests/data diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b6bfe39..ab07a53 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: python-version: ${{matrix.python-version}} cache: pip - - run: pip${{matrix.python-version}} install vmklib>=1.8.0 + - run: pip${{matrix.python-version}} install vmklib # Begin project-specific setup. - uses: seanmiddleditch/gha-setup-ninja@master @@ -62,7 +62,7 @@ jobs: env: PY_TEST_EXTRA_ARGS: --cov-report=xml - - uses: codecov/codecov-action@main + - uses: codecov/codecov-action@v3 - run: mk pypi-upload-ci env: @@ -75,7 +75,7 @@ jobs: - run: | mk python-release owner=vkottler \ - repo=ifgen version=1.2.0 + repo=ifgen version=2.0.0 if: | matrix.python-version == '3.11' && matrix.system == 'ubuntu-latest' diff --git a/.isort.cfg b/.isort.cfg index fed3605..3ae85b4 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,2 +1,3 @@ [settings] known_first_party=ifgen,vmklib +skip=tests/data diff --git a/.pylintrc b/.pylintrc index 295a140..1a532b3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,5 @@ [DESIGN] max-args=7 + +[MESSAGES CONTROL] +disable=duplicate-code diff --git a/README.md b/README.md index dcb1184..00e0baf 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.3 - hash=5e7b7fed8b0401fcfa361931270f999b + hash=6281f97e0bcb4e95cd6f93142f045f34 ===================================== --> -# ifgen ([1.2.0](https://pypi.org/project/ifgen/)) +# ifgen ([2.0.0](https://pypi.org/project/ifgen/)) [![python](https://img.shields.io/pypi/pyversions/ifgen.svg)](https://pypi.org/project/ifgen/) ![Build Status](https://github.com/vkottler/ifgen/workflows/Python%20Package/badge.svg) @@ -46,7 +46,8 @@ This package is tested on the following platforms: ``` $ ./venv3.11/bin/ig -h -usage: ig [-h] [--version] [-v] [-C DIR] {gen,noop} ... +usage: ig [-h] [--version] [-v] [-q] [--curses] [--no-uvloop] [-C DIR] + {gen,noop} ... An interface generator for distributed computing. @@ -54,6 +55,9 @@ options: -h, --help show this help message and exit --version show program's version number and exit -v, --verbose set to increase logging verbosity + -q, --quiet set to reduce output + --curses whether or not to use curses.wrapper when starting + --no-uvloop whether or not to disable uvloop as event loop driver -C DIR, --dir DIR execute from a specific directory commands: diff --git a/config b/config index eaf4386..e575ffe 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit eaf4386563e1287763639ed52ebf1ded2686938b +Subproject commit e575ffef31a4a26732d60891ae83d9209666c840 diff --git a/ifgen/__init__.py b/ifgen/__init__.py index 061b040..7df3b47 100644 --- a/ifgen/__init__.py +++ b/ifgen/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.3 -# hash=dcfab6b503a06109b92ab9ee84f0bec4 +# hash=a5fbdd144be22177fbedbc33d8b58b3d # ===================================== """ @@ -10,4 +10,4 @@ DESCRIPTION = "An interface generator for distributed computing." PKG_NAME = "ifgen" -VERSION = "1.2.0" +VERSION = "2.0.0" diff --git a/ifgen/common/__init__.py b/ifgen/common/__init__.py index 9765e28..2fcdcbe 100644 --- a/ifgen/common/__init__.py +++ b/ifgen/common/__init__.py @@ -17,7 +17,19 @@ def create_common_test(task: GenerateTask) -> None: def create_common(task: GenerateTask) -> None: """Create a unit test for the enum string-conversion methods.""" - with task.boilerplate(includes=[""]) as writer: + streams = task.stream_implementation + + includes = [ + "", + "", + "" if not streams else "", + ] + + # probably get rid of everything besides the spanstream + if streams: + includes.extend(["", "", ""]) + + with task.boilerplate(includes=includes) as writer: writer.c_comment("Enforce that this isn't a mixed-endian system.") writer.write( "static_assert(std::endian::native == std::endian::big or" @@ -25,3 +37,28 @@ def create_common(task: GenerateTask) -> None: writer.write( " std::endian::native == std::endian::little);" ) + + with writer.padding(): + writer.c_comment("Create useful aliases for bytes.") + writer.write("template ") + writer.write("using byte_span = std::span;") + writer.write( + ( + "template using byte_array = " + "std::array;" + ) + ) + + if streams: + writer.c_comment("Abstract byte-stream interfaces.") + writer.write("using byte_istream = std::basic_istream;") + writer.write("using byte_ostream = std::basic_ostream;") + + writer.empty() + writer.c_comment( + "Concrete byte-stream interfaces (based on span)." + ) + writer.write("using byte_spanbuf = std::basic_spanbuf;") + writer.write( + "using byte_spanstream = std::basic_spanstream;" + ) diff --git a/ifgen/data/schemas/Config.yaml b/ifgen/data/schemas/Config.yaml index 6fea198..0299eab 100644 --- a/ifgen/data/schemas/Config.yaml +++ b/ifgen/data/schemas/Config.yaml @@ -61,3 +61,7 @@ properties: ifgen: type: object + + stream_implementation: + type: boolean + default: true diff --git a/ifgen/entry.py b/ifgen/entry.py index fa9f028..ed62f78 100644 --- a/ifgen/entry.py +++ b/ifgen/entry.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.3 -# hash=6018c3513f5723ef6420ac8718d5f8ad +# hash=089f57617fd119bfbae72f24dfa671c7 # ===================================== """ @@ -10,14 +10,14 @@ # built-in import argparse -import logging +from logging import getLogger import os from pathlib import Path import sys from typing import List # third-party -from vcorelib.logging import log_time as _log_time +from vcorelib.logging import init_logging, log_time, logging_args # internal from ifgen import DESCRIPTION, VERSION @@ -41,12 +41,7 @@ def main(argv: List[str] = None) -> int: action="version", version=f"%(prog)s {VERSION}", ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="set to increase logging verbosity", - ) + logging_args(parser) parser.add_argument( "-C", "--dir", @@ -66,17 +61,15 @@ def main(argv: List[str] = None) -> int: args.dir = args.dir.resolve() # initialize logging - log_level = logging.DEBUG if args.verbose else logging.INFO - logging.basicConfig( - level=log_level, - format="%(name)-36s - %(levelname)-6s - %(message)s", + init_logging( + args, default_format="%(name)-36s - %(levelname)-6s - %(message)s" ) # change to the specified directory os.chdir(args.dir) # run the application - with _log_time(logging.getLogger(__name__), "Command"): + with log_time(getLogger(__name__), "Command"): result = entry(args) except SystemExit as exc: result = 1 diff --git a/ifgen/generation/interface.py b/ifgen/generation/interface.py index 3d96560..5913a5d 100644 --- a/ifgen/generation/interface.py +++ b/ifgen/generation/interface.py @@ -14,6 +14,7 @@ Iterator, NamedTuple, Optional, + Union, ) # third-party @@ -59,6 +60,13 @@ def cpp_namespace( data = f"{self.name}::{data}" return data if not prefix else prefix + data + @property + def stream_implementation(self) -> bool: + """ + Determine if this instances should include a stream implementations. + """ + return self.env.config.data["stream_implementation"] # type: ignore + @property def source_path(self) -> Path: """Get a source file for this task.""" @@ -79,11 +87,11 @@ def protocol(self) -> Protocol: return self.env.get_protocol(self.name) - def namespace(self) -> str: + def namespace(self, *names: str) -> str: """Get this task's namespace.""" nspace = self.env.types.root_namespace - with nspace.pushed(*self.instance.get("namespace", [])): + with nspace.pushed(*self.instance.get("namespace", []), *names): result = nspace.namespace(track=False) assert result, f"No namespace for '{self.name}'!" @@ -198,7 +206,7 @@ def boilerplate( includes: Iterable[str] = None, is_test: bool = False, use_namespace: bool = True, - description: str = None, + description: Union[bool, None, str] = None, json: bool = False, ) -> Iterator[IndentedFileWriter]: """ @@ -232,9 +240,9 @@ def boilerplate( description = self.instance["description"] # Write struct definition. - if description is not None: + if description: with writer.javadoc(): - writer.write(description) + writer.write(description) # type: ignore yield writer diff --git a/ifgen/generation/test.py b/ifgen/generation/test.py index e2dbe06..eaefc5d 100644 --- a/ifgen/generation/test.py +++ b/ifgen/generation/test.py @@ -2,9 +2,8 @@ A module implementing unit-testing related generation utilities. """ -from contextlib import contextmanager - # built-in +from contextlib import ExitStack, contextmanager from os import linesep from typing import Iterator, List @@ -15,9 +14,59 @@ from ifgen.generation.interface import GenerateTask +def unit_test_method_name(name: str, task: GenerateTask) -> str: + """Get the name of a unit test.""" + return f"test_{task.name}_{name}" + + +@contextmanager +def unit_test_method( + name: str, task: GenerateTask, writer: IndentedFileWriter +) -> Iterator[None]: + """Generate unit-test method boilerplate.""" + + unit_test_method_name(name, task) + writer.write( + f"void {unit_test_method_name(name, task)}(std::endian endianness)" + ) + with writer.scope(): + nspace = task.env.types.root_namespace + + project_wide = nspace.namespace(track=False) + writer.write(f"using namespace {project_wide};") + + with nspace.pushed(*task.instance.get("namespace", [])): + curr = nspace.namespace(track=False) + if curr != project_wide: + writer.write(f"using namespace {curr};") + + writer.empty() + yield + + +@contextmanager +def unit_test_main( + task: GenerateTask, writer: IndentedFileWriter, description: bool = True +) -> Iterator[None]: + """A method for generating main-function boilerplate for unit tests.""" + + if description: + with writer.javadoc(): + writer.write(f"A unit test for {task.generator} {task.name}.") + writer.empty() + writer.write("\\return 0 on success.") + + writer.write("int main(void)") + with writer.scope(): + writer.write(f"using namespace {task.namespace()};") + writer.empty() + yield + writer.write("return 0;") + + @contextmanager def unit_test_boilerplate( - task: GenerateTask, includes: List[str] = None + task: GenerateTask, includes: List[str] = None, main: bool = True ) -> Iterator[IndentedFileWriter]: """Handle standard unit-test boilerplate.""" @@ -28,22 +77,23 @@ def unit_test_boilerplate( linesep.join([f"A unit test for {task.generator} {task.name}."]) - with task.boilerplate( - includes=["", "", "", f'"{include}"'] - + includes, - is_test=True, - use_namespace=False, - description=linesep.join( - [ - f"A unit test for {task.generator} {task.name}.", - "", - "\\return 0 on success.", - ] - ), - ) as writer: - writer.write("int main(void)") - with writer.scope(): - writer.write(f"using namespace {task.namespace()};") - writer.empty() - yield writer - writer.write("return 0;") + with ExitStack() as stack: + writer = stack.enter_context( + task.boilerplate( + includes=[ + "", + "", + "", + f'"{include}"', + ] + + includes, + is_test=True, + use_namespace=False, + description=False, + ) + ) + + if main: + stack.enter_context(unit_test_main(task, writer)) + + yield writer diff --git a/ifgen/struct/__init__.py b/ifgen/struct/__init__.py index b1c5f53..1329384 100644 --- a/ifgen/struct/__init__.py +++ b/ifgen/struct/__init__.py @@ -13,6 +13,7 @@ from ifgen.generation.interface import GenerateTask from ifgen.struct.methods import struct_methods from ifgen.struct.source import create_struct_source +from ifgen.struct.stream import struct_stream_methods from ifgen.struct.test import create_struct_test __all__ = ["create_struct", "create_struct_test", "create_struct_source"] @@ -25,26 +26,9 @@ def struct_line(name: str, value: FieldConfig) -> LineWithComment: return f"{value['type']} {name};", value.get("description") # type: ignore -TYPE_LOOKUP: Dict[str, str] = {} -for _item in [ - "int8_t", - "int16_t", - "int32_t", - "int64_t", - "uint8_t", - "uint16_t", - "uint32_t", - "uint64_t", -]: - TYPE_LOOKUP[_item] = "" - - def header_for_type(name: str, task: GenerateTask) -> str: """Determine the header file to import for a given type.""" - if name in TYPE_LOOKUP: - return TYPE_LOOKUP[name] - candidate = task.custom_include(name) if candidate: return f'"{candidate}"' @@ -61,7 +45,6 @@ def struct_includes(task: GenerateTask) -> Iterable[str]: } result.add(f'"../{PKG_NAME}/common.h"') - result.add("") return result @@ -109,9 +92,10 @@ def create_struct(task: GenerateTask) -> None: writer.c_comment("Methods.") struct_methods(task, writer, True) - writer.empty() - # Add size assertion. - writer.write( - f"static_assert(sizeof({task.name}) == {task.name}::size);" - ) + with writer.padding(): + writer.write( + f"static_assert(sizeof({task.name}) == {task.name}::size);" + ) + + struct_stream_methods(task, writer, True) diff --git a/ifgen/struct/methods/__init__.py b/ifgen/struct/methods/__init__.py index bbec5a9..4b00f1b 100644 --- a/ifgen/struct/methods/__init__.py +++ b/ifgen/struct/methods/__init__.py @@ -25,26 +25,92 @@ def protocol_json(task: GenerateTask) -> dict[str, Any]: return protocol.export_json() -def struct_buffer_method( +def span_method( task: GenerateTask, writer: IndentedFileWriter, header: bool +) -> None: + """Generate a span method.""" + + if header: + with writer.javadoc(): + writer.write(("Get this instance as a byte span.")) + + span_type = task.cpp_namespace("Span", header=header) + method = task.cpp_namespace("span", header=header) + + writer.write(f"{span_type} {method}()" + (";" if header else "")) + + if header: + return + + with writer.scope(): + writer.write("return Span(*raw());") + + +def struct_buffer_method( + task: GenerateTask, + writer: IndentedFileWriter, + header: bool, + read_only: bool, ) -> None: """Generate a method for raw buffer access.""" if header: with writer.javadoc(): - writer.write("Get this instance as a fixed-size byte array.") + writer.write( + ( + "Get this instance as a " + f"{'read-only ' if read_only else ''}" + "fixed-size byte array." + ) + ) buff_type = task.cpp_namespace("Buffer", header=header) + if read_only: + buff_type = "const " + buff_type + # Returns a pointer. - method = task.cpp_namespace("raw()", prefix="*", header=header) - writer.write(f"{buff_type} {method}" + (";" if header else "")) + method = task.cpp_namespace( + "raw()" if not read_only else "raw_ro()", prefix="*", header=header + ) + writer.write( + f"{buff_type} {method}" + + (" const" if read_only else "") + + (";" if header else "") + ) if header: return with writer.scope(): - writer.write("return reinterpret_cast(this);") + writer.write( + "return reinterpret_cast" + f"<{'const ' if read_only else ''}Buffer *>(this);" + ) + + +def swap_method( + task: GenerateTask, writer: IndentedFileWriter, header: bool +) -> None: + """Add an in-place swap method.""" + + if header: + with writer.javadoc(): + writer.write("Swap this instance's bytes in place.") + writer.empty() + writer.write( + task.command("return", "A reference to the instance.") + ) + + method = task.cpp_namespace("swap", header=header) + writer.write(f"const {task.name} &{method}()" + (";" if header else "")) + + if header: + return + + with writer.scope(): + writer.write("encode_swapped(raw());") + writer.write("return *this;") def struct_methods( @@ -53,18 +119,26 @@ def struct_methods( """Write generated-struct methods.""" if header: - writer.write("using Buffer = std::array;") + writer.write("using Buffer = byte_array;") + writer.write("using Span = byte_span;") with writer.padding(): writer.write( f"auto operator<=>(const {task.name} &) const = default;" ) - struct_buffer_method(task, writer, header) + struct_buffer_method(task, writer, header, False) + writer.empty() + span_method(task, writer, header) + + with writer.padding(): + struct_buffer_method(task, writer, header, True) struct_encode(task, writer, header) - writer.empty() + with writer.padding(): + swap_method(task, writer, header) + struct_decode(task, writer, header) to_json_method( diff --git a/ifgen/struct/methods/common.py b/ifgen/struct/methods/common.py index c136af4..9896c78 100644 --- a/ifgen/struct/methods/common.py +++ b/ifgen/struct/methods/common.py @@ -19,7 +19,7 @@ def native_decode(writer: IndentedFileWriter) -> None: def native_encode(writer: IndentedFileWriter) -> None: """Write a buffer encoding method for native byte order.""" - writer.write("*buffer = *raw();") + writer.write("*buffer = *raw_ro();") def wrapper_method( @@ -49,6 +49,10 @@ def wrapper_method( if header: line += " = std::endian::native" line += ")" + + if is_encode: + line += " const" + if header: line += ";" diff --git a/ifgen/struct/methods/swap.py b/ifgen/struct/methods/swap.py index 3e723dc..3eacba8 100644 --- a/ifgen/struct/methods/swap.py +++ b/ifgen/struct/methods/swap.py @@ -30,13 +30,12 @@ def no_swap( arg = "buf[idx++]" if is_enum: arg = f"{kind}({arg})" + else: + arg = f"std::to_integer<{kind}>({arg})" writer.write(line + arg + ";") else: - arg = name - if is_enum: - arg = f"uint8_t({arg})" - writer.write(f"buf[idx++] = {arg};") + writer.write(f"buf[idx++] = std::byte({name});") def swap_struct( @@ -125,7 +124,7 @@ def encode_primitive_swap( rhs += f"{field['name']})" else: lhs = f"*reinterpret_cast<{integral} *>(&buf[idx])" - rhs += f"reinterpret_cast<{integral} &>({field['name']}))" + rhs += f"reinterpret_cast({field['name']}))" assignment(writer, lhs, rhs) @@ -233,7 +232,7 @@ def encode_swapped_method( method = task.cpp_namespace("encode_swapped", header=header) writer.write( - f"std::size_t {method}(Buffer *buffer)" + (";" if header else "") + f"std::size_t {method}(Buffer *buffer) const" + (";" if header else "") ) if header: diff --git a/ifgen/struct/source.py b/ifgen/struct/source.py index fe3a91c..4adc943 100644 --- a/ifgen/struct/source.py +++ b/ifgen/struct/source.py @@ -5,6 +5,7 @@ # internal from ifgen.generation.interface import GenerateTask from ifgen.struct.methods import struct_methods +from ifgen.struct.stream import struct_stream_methods def create_struct_source(task: GenerateTask) -> None: @@ -12,3 +13,5 @@ def create_struct_source(task: GenerateTask) -> None: with task.source_boilerplate([]) as writer: struct_methods(task, writer, False) + writer.empty() + struct_stream_methods(task, writer, False) diff --git a/ifgen/struct/stream.py b/ifgen/struct/stream.py new file mode 100644 index 0000000..fd7d430 --- /dev/null +++ b/ifgen/struct/stream.py @@ -0,0 +1,70 @@ +""" +A module for implementing struct stream-related methods. +""" + +# third-party +from vcorelib.io.file_writer import IndentedFileWriter + +# internal +from ifgen.generation.interface import GenerateTask + + +def struct_istream( + task: GenerateTask, writer: IndentedFileWriter, header: bool +) -> None: + """Generate an input-stream handling method.""" + + writer.write( + ( + "byte_istream &operator>>" + f"(byte_istream &stream, {task.name} &instance)" + ) + + (";" if header else "") + ) + + if header: + return + + with writer.scope(): + writer.write( + f"stream.read(instance.raw()->data(), {task.name}::size);" + ) + writer.write("return stream;") + + +def struct_ostream( + task: GenerateTask, writer: IndentedFileWriter, header: bool +) -> None: + """Generate an output-stream handling method.""" + + writer.write( + ( + "byte_ostream &operator<<" + f"(byte_ostream &stream, const {task.name} &instance)" + ) + + (";" if header else "") + ) + + if header: + return + + with writer.scope(): + writer.write( + f"stream.write(instance.raw_ro()->data(), {task.name}::size);" + ) + writer.write("return stream;") + + +def struct_stream_methods( + task: GenerateTask, writer: IndentedFileWriter, header: bool +) -> None: + """Generate struct stream read and write methods.""" + + writer.c_comment("Stream interfaces.") + + struct_istream(task, writer, header) + + if not header: + writer.empty() + + struct_ostream(task, writer, header) diff --git a/ifgen/struct/test.py b/ifgen/struct/test.py index 3b96c80..73ba834 100644 --- a/ifgen/struct/test.py +++ b/ifgen/struct/test.py @@ -7,12 +7,91 @@ # internal from ifgen.generation.interface import GenerateTask -from ifgen.generation.test import unit_test_boilerplate +from ifgen.generation.test import ( + unit_test_boilerplate, + unit_test_main, + unit_test_method, + unit_test_method_name, +) + + +def assert_line(writer: IndentedFileWriter, data: str) -> None: + """Write an assert line to the file.""" + writer.write(f"assert({data});") + + +def unit_test_stream_tests( + task: GenerateTask, writer: IndentedFileWriter +) -> None: + """Generate unit tests for stream interfaces.""" + + writer.c_comment("Test stream interactions.") + + writer.write(f"byte_array<{task.name}::size * len> streambuf;") + writer.write(f"using TestSpan = byte_span<{task.name}::size * len>;") + writer.write("auto stream = byte_spanstream(TestSpan(streambuf));") + + with writer.padding(): + writer.write("stream << dst;") + writer.write("stream.seekg(0);") + + writer.write(f"{task.name} from_stream;") + writer.write("stream >> from_stream;") + + +def unit_test_basic_method( + task: GenerateTask, writer: IndentedFileWriter +) -> None: + """ + Implement a simple encode-and-decode scenario with endianness as a + function argument. + """ + + with unit_test_method("encode_decode_basic", task, writer): + writer.write("static constexpr std::size_t len = 10;") + writer.empty() + + nspaced = task.name + writer.write(f"{nspaced} src;") + assert_line(writer, f"src.span().size() == {nspaced}::size") + + with writer.padding(): + writer.write("src.swap();") + + writer.c_comment("Eventually, we could assign member values here.") + + with writer.padding(): + writer.write(f"{nspaced}::Buffer buffer;") + assert_line( + writer, f"src.encode(&buffer, endianness) == {nspaced}::size" + ) + + writer.write(f"{nspaced} dst;") + assert_line( + writer, f"dst.decode(&buffer, endianness) == {nspaced}::size" + ) + assert_line(writer, "src == dst") + + with writer.padding(): + writer.c_comment("Verify the values transferred.") + + unit_test_stream_tests(task, writer) def unit_test_body(task: GenerateTask, writer: IndentedFileWriter) -> None: """Implement a unit test for a struct.""" + for method in ["encode_decode_basic"]: + for arg in [ + "std::endian::native", + "std::endian::little", + "std::endian::big", + ]: + name = unit_test_method_name(method, task) + writer.write(f"{name}({arg});") + + writer.empty() + writer.c_comment("Attempt to decode this?") writer.write(f"std::cout << {task.name}::json();") @@ -20,6 +99,10 @@ def unit_test_body(task: GenerateTask, writer: IndentedFileWriter) -> None: def create_struct_test(task: GenerateTask) -> None: """Create a unit test for the enum string-conversion methods.""" - with unit_test_boilerplate(task) as writer: - unit_test_body(task, writer) + with unit_test_boilerplate(task, main=False) as writer: + unit_test_basic_method(task, writer) + writer.empty() + + with unit_test_main(task, writer): + unit_test_body(task, writer) diff --git a/local/variables/package.yaml b/local/variables/package.yaml index 33312d5..404a202 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- -major: 1 -minor: 2 +major: 2 +minor: 0 patch: 0 entry: ig diff --git a/pyproject.toml b/pyproject.toml index 5e8b5dc..7f3973d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "ifgen" -version = "1.2.0" +version = "2.0.0" description = "An interface generator for distributed computing." readme = "README.md" requires-python = ">=3.11" diff --git a/tests/data/valid/scenarios/sample/.gitignore b/tests/data/valid/scenarios/sample/.gitignore index 59ca08f..8a6df19 100644 --- a/tests/data/valid/scenarios/sample/.gitignore +++ b/tests/data/valid/scenarios/sample/.gitignore @@ -1,2 +1,6 @@ src/generated src/apps/generated +tasks +README.md +LICENSE +.github diff --git a/tests/data/valid/scenarios/sample/config b/tests/data/valid/scenarios/sample/config new file mode 120000 index 0000000..a61e19e --- /dev/null +++ b/tests/data/valid/scenarios/sample/config @@ -0,0 +1 @@ +../../../../../config \ No newline at end of file diff --git a/tests/data/valid/scenarios/sample/local/configs/license.yaml b/tests/data/valid/scenarios/sample/local/configs/license.yaml new file mode 100644 index 0000000..a01a5a7 --- /dev/null +++ b/tests/data/valid/scenarios/sample/local/configs/license.yaml @@ -0,0 +1,2 @@ +--- +name: Vaughn Kottler diff --git a/tests/data/valid/scenarios/sample/local/configs/project.yaml b/tests/data/valid/scenarios/sample/local/configs/project.yaml new file mode 100644 index 0000000..e790884 --- /dev/null +++ b/tests/data/valid/scenarios/sample/local/configs/project.yaml @@ -0,0 +1,4 @@ +--- +name: sample +description: A project for unit-testing with. +version: {major: 0, minor: 1, patch: 0} diff --git a/tests/data/valid/scenarios/sample/manifest.yaml b/tests/data/valid/scenarios/sample/manifest.yaml new file mode 100644 index 0000000..47acb2e --- /dev/null +++ b/tests/data/valid/scenarios/sample/manifest.yaml @@ -0,0 +1,19 @@ +--- +default_target: groups-all + +includes: + - config/includes/license.yaml + - config/includes/funding.yaml + - config/includes/yambs.yaml + +compiles: + - name: local + configs: + - local/configs + +groups: + - name: all + dependencies: + - groups-license + - groups-funding + - groups-yambs-native-renders-minimal diff --git a/tests/data/valid/scenarios/sample/src/apps/test_structs.cc b/tests/data/valid/scenarios/sample/src/apps/test_structs.cc index 63bc1f8..6af14f2 100644 --- a/tests/data/valid/scenarios/sample/src/apps/test_structs.cc +++ b/tests/data/valid/scenarios/sample/src/apps/test_structs.cc @@ -9,6 +9,8 @@ #include #include +static constexpr std::size_t len = 10; + void test1_encode_decode(std::endian endianness) { using namespace A::B; @@ -29,6 +31,30 @@ void test1_encode_decode(std::endian endianness) assert(dst.field1 == 0x55); assert(dst.field2 == Enum1::C); assert(dst.field3 == 2.5f); + + std::array streambuf; + using TestSpan = std::span; + auto stream = byte_spanstream(TestSpan(streambuf)); + + stream << dst; + stream.seekg(0); + + C::Test1 from_stream; + stream >> from_stream; + + /* Verify the values transferred. */ + assert(from_stream.field1 == 0x55); + assert(from_stream.field2 == Enum1::C); + assert(from_stream.field3 == 2.5f); + + for (std::size_t i = 0; i < len - 1; i++) + { + stream << from_stream; + } + assert(stream.good()); + + stream << from_stream; + assert(not stream.good()); } void test2_encode_decode(std::endian endianness)