Conformance test suite (#44)

Conformance test suite
This commit is contained in:
Ludovic Chenut 2023-02-21 12:06:23 +01:00 committed by GitHub
parent bd4520d96d
commit 43f77303bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2625 additions and 110 deletions

View File

@ -30,11 +30,11 @@ jobs:
include:
- target:
os: linux
builder: ubuntu-18.04
builder: ubuntu-20.04
shell: bash
- target:
os: macos
builder: macos-10.15
builder: macos-12
shell: bash
- target:
os: windows
@ -96,7 +96,7 @@ jobs:
- name: Restore Nim DLLs dependencies (Windows) from cache
if: runner.os == 'Windows'
id: windows-dlls-cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: external/dlls-${{ matrix.target.cpu }}
key: 'dlls-${{ matrix.target.cpu }}'

101
.github/workflows/conformance.yml vendored Normal file
View File

@ -0,0 +1,101 @@
name: Conformance Test
on:
workflow_dispatch:
types: [run]
pull_request:
concurrency: # Cancel stale PR builds (but not push builds)
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
build:
strategy:
fail-fast: false
matrix:
target:
- os: linux
cpu: amd64
- os: linux
cpu: i386
branch: [version-1-4, version-1-6, devel]
include:
- target:
os: linux
builder: ubuntu-20.04
shell: bash
defaults:
run:
shell: ${{ matrix.shell }}
name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})'
runs-on: ${{ matrix.builder }}
continue-on-error: ${{ matrix.branch == 'devel' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install build dependencies (Linux i386)
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
run: |
sudo dpkg --add-architecture i386
sudo apt-fast update -qq
sudo DEBIAN_FRONTEND='noninteractive' apt-fast install \
--no-install-recommends -yq gcc-multilib g++-multilib \
libssl-dev:i386
mkdir -p external/bin
cat << EOF > external/bin/gcc
#!/bin/bash
exec $(which gcc) -m32 "\$@"
EOF
cat << EOF > external/bin/g++
#!/bin/bash
exec $(which g++) -m32 "\$@"
EOF
chmod 755 external/bin/gcc external/bin/g++
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
- name: Derive environment variables
run: |
if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then
PLATFORM=x64
else
PLATFORM=x86
fi
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
ncpu=
MAKE_CMD="make"
case '${{ runner.os }}' in
'Linux')
ncpu=$(nproc)
;;
esac
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
echo "ncpu=$ncpu" >> $GITHUB_ENV
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
- name: Install CMake
run: |
if [ "${{ runner.os }}" == "Linux" ]; then
sudo apt-get install cmake
fi
- name: Build Nim and Nimble
run: |
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
bash build_nim.sh nim csources dist/nimble NimBinaries
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
- name: Run tests
run: |
nim --version
nimble --version
nimble install -y --depsOnly
env NIMLANG="c" nimble conformance_test
env NIMLANG="cpp" nimble conformance_test

View File

@ -50,3 +50,20 @@ task test, "Run all tests":
echo " \x1B[0;31m[FAILED]\x1B[0;37m ", path.split(DirSep)[^1]
exec "exit 1"
task conformance_test, "Run conformance tests":
let
pwd = thisDir()
conformance = pwd / "conformance"
test = pwd / "tests" / "conformance"
if not system.dirExists(conformance):
exec "git clone -b v22.0 --recurse-submodules https://github.com/protocolbuffers/protobuf/ " & conformance
withDir conformance:
exec "cmake . -Dprotobuf_BUILD_CONFORMANCE=ON"
exec "make conformance_test_runner"
exec "cp " & conformance / "conformance_test_runner" & " " & test
withDir test:
exec "nim c -d:ConformanceTest conformance_nim.nim"
exec "./conformance_test_runner --failure_list failure_list.txt conformance_nim"

View File

@ -31,6 +31,8 @@ type
# EndGroup = 4 # Not used
Fixed32 = 5
SomePBInt* = int32 | int64 | uint32 | uint64
FieldHeader* = distinct uint32
# Scalar types used in `.proto` files
@ -54,19 +56,20 @@ type
sfixed64* = distinct int64 ## fixed-width signed integer
pbool* = distinct bool
penum* = distinct int32
pstring* = distinct string ## UTF-8-encoded string
pbytes* = distinct seq[byte] ## byte sequence
SomeScalar* =
pint32 | pint64 | puint32 | puint64 | sint32 | sint64 | pbool |
pint32 | pint64 | puint32 | puint64 | sint32 | sint64 | pbool | penum |
fixed64 | sfixed64 | pdouble |
pstring | pbytes |
fixed32 | sfixed32 | pfloat
# Mappings of proto type to wire type
SomeVarint* =
pint32 | pint64 | puint32 | puint64 | sint32 | sint64 | pbool
pint32 | pint64 | puint32 | puint64 | sint32 | sint64 | pbool | penum
SomeFixed64* = fixed64 | sfixed64 | pdouble
SomeLengthDelim* = pstring | pbytes # Also messages and packed repeated fields
SomeFixed32* = fixed32 | sfixed32 | pfloat
@ -116,6 +119,7 @@ func toUleb(x: sint32): uint32 =
template toUleb(x: pint64): uint64 = cast[uint64](x)
template toUleb(x: pint32): uint32 = cast[uint32](x)
template toUleb(x: pbool): uint8 = cast[uint8](x)
template toUleb(x: penum): uint64 = cast[uint32](x)
template fromUleb(x: uint64, T: type puint64): T = puint64(x)
template fromUleb(x: uint64, T: type pbool): T = pbool(x != 0)
@ -130,6 +134,7 @@ template fromUleb(x: uint64, T: type sint32): T =
template fromUleb(x: uint64, T: type pint64): T = cast[T](x)
template fromUleb(x: uint64, T: type pint32): T = cast[T](x)
template fromUleb(x: uint64, T: type penum): T = cast[T](x)
template toBytes*(x: SomeVarint): openArray[byte] =
toBytes(toUleb(x), Leb128).toOpenArray()

View File

@ -26,7 +26,7 @@ type
ReservedType* = enum
String, Number, Range
ProtoType* = enum
Field, Enum, EnumVal, ReservedBlock, Reserved, Message, File, Imported, Oneof, Package, ProtoDef
Field, Enum, EnumVal, ReservedBlock, Reserved, Message, File, Imported, Oneof, Package, ProtoDef, Extend
Presence* = enum
Singular, Repeated, Optional, Required
ProtoNode* = ref object
@ -62,6 +62,9 @@ type
definedEnums*: seq[ProtoNode]
fields*: seq[ProtoNode]
nested*: seq[ProtoNode]
of Extend:
extending*: string
extendedFields*: seq[ProtoNode]
of Package:
packageName*: string
messages*: seq[ProtoNode]
@ -148,6 +151,14 @@ proc `$`*(node: ProtoNode): string =
messages &= $message & "\n"
data &= messages[0..^2].indent(1, " ")
result &= data.indent(1, " ")
of Extend:
result = "Extension of $1 with fields:".format(node.extending)
var data = ""
var fields = "\n"
for field in node.extendedFields:
fields &= $field & "\n"
data &= fields[0..^2].indent(1, " ")
result &= data.indent(1, " ")
of File:
result = "Protobuf file with syntax $1\n".format(
node.syntax)

View File

@ -9,6 +9,7 @@ type
typ: TokenType
text: string
index: int
filePos: int
proc `$`(t: Token): string =
$t.typ & ": " & t.text
@ -22,12 +23,13 @@ proc `==`(t: Token, tt: TokenType): bool =
proc `==`(t: Token, c: char): bool =
t.typ == Symbol and t.text[0] == c
proc tokenize(text: string): seq[Token] =
proc tokenize(filename, text: string): seq[Token] =
let lexer = peg(tokens, res: seq[Token]):
space <- +Space
comment <- "//" * *(1 - '\n')
comment2 <- "/*" * @"*/"
decDigit <- {'0'..'9'}
octDigit <- {'0'..'7'}
hexDigit <- {'0'..'9', 'a'..'f', 'A'..'F'}
octEscape <- '\\' * octDigit[3]
@ -35,21 +37,27 @@ proc tokenize(text: string): seq[Token] =
charEscape <- '\\' * {'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"'}
escapes <- hexEscape | octEscape | charEscape
dblstring <- '"' * *(escapes | 1 - '"') * '"':
res.add Token(typ: String, text: ($0).unescape)
res.add Token(typ: String, text: ($0).unescape, filePos: @0)
regstring <- '\'' * *(escapes | 1 - '\'') * '\'':
res.add Token(typ: String, text: ($0).unescape(prefix = "'", suffix = "'"))
res.add Token(typ: String, text: ($0).unescape(prefix = "'", suffix = "'"), filePos: @0)
string <- dblstring | regstring
sym <- {'=', ';', '{', '}', '[', ']', '<', '>', ','}:
res.add Token(typ: Symbol, text: $0)
res.add Token(typ: Symbol, text: $0, filePos: @0)
# according to the official syntax, this is correct.
# but in reality, leading underscores are accepted
#ident <- Alpha * *(Alpha | '_' | Digit)
ident <- (Alpha | '_') * *(Alpha | '_' | Digit)
fullIdent <- ?'.' * ident * *('.' * ident):
res.add Token(typ: Ident, text: $0)
res.add Token(typ: Ident, text: $0, filePos: @0)
decimals <- +decDigit
exponent <- {'e', 'E'} * ?('+' | '-') * decimals
floatLit <- ((decimals * '.' * ?decimals * ?exponent) | (decimals * exponent) | ('.' * decimals * ?exponent)):
# TODO: find a way to add "inf" and "nan", currently if a Field name starts with nan or inf, it will fail to parse
res.add Token(typ: Float, text: $0, filePos: @0)
int <- ?('-' | '+') * +(Digit):
res.add Token(typ: Integer, text: $0)
tokens <- *(comment | comment2 | space | string | sym | int | fullIdent)
res.add Token(typ: Integer, text: $0, filePos: @0)
tokens <- *(comment | comment2 | space | string | sym | floatLit | int | fullIdent)
let
match = lexer.match(text, result)
@ -58,11 +66,13 @@ proc tokenize(text: string): seq[Token] =
tok.index = index
if match.matchLen != text.len:
var msg = "Failed to lex: '" & text[match.matchMax] & "'\n"
let
minToShow = max(0, match.matchMax - 20)
maxToShow = min(text.len, match.matchMax + 20)
msg &= text[minToShow .. maxToShow]
let line = text[0..<match.matchMax].count('\n') + 1
var msg = filename & ":" & $line & " Failed to lex '" & text[match.matchMax] & "'\n"
var
minToShow = text.rfind('\n', 0, match.matchMax) + 1
maxToShow = text.find('\n', match.matchMax)
if maxToShow == -1: maxToShow = text.len
msg &= text[minToShow ..< maxToShow]
raise newException(CatchableError, msg)
type
@ -92,21 +102,25 @@ proc extract(x: var seq[(Token, ProtoNode)], s: Token): seq[ProtoNode] =
x.keepItIf(it[0].index < s.index)
proc parseProtoPackage(file: string, toImport: var HashSet[string]): ProtoNode =
let tokens = file.readFile.tokenize
let
fileContent = file.readFile
filename = file.extractFilename
tokens = tokenize(filename, fileContent)
let parser = peg(g, Token, ps: ParseState):
ident <- [Ident]
string <- [String]
int <- [Integer]
float <- [Float]
pkg <- ["package"] * >ident * [';']:
ps.currentPackage = ProtoNode(kind: Package, packageName: ($1).text)
option <- ["option"] * ident * ['='] * (ident | string) * [';']
option <- ["option"] * ident * ['='] * (ident | string | int | float) * [';']
syntax <- ["syntax"] * ['='] * string * [';']
impor <- ["import"] * >string * [';']:
ps.imports.add ProtoNode(kind: Imported, filename: ($1).text)
fieldopt <- ident * ['='] * ident
fieldopt <- ident * ['='] * (ident | int | string | float)
fieldopts <- ['['] * fieldopt * *([','] * fieldopt) * [']']
oneoffield <- >ident * >ident * ['='] * >int * ?fieldopts * [';']:
@ -178,7 +192,7 @@ proc parseProtoPackage(file: string, toImport: var HashSet[string]): ProtoNode =
ps.fields.add (($0, node))
mapfield <- ["map"] * ['<'] * >ident * [','] * >ident * ['>'] * * >ident * ['='] * >int * ?fieldopts * [';']:
let
fieldType = "map<" & ($1).text & ", " & ($2).text & ">"
fieldType = "map<" & ($1).text & "," & ($2).text & ">"
fieldName = ($3).text
fieldValue = ($4).text
ps.fields.add (($0, ProtoNode(
@ -186,7 +200,42 @@ proc parseProtoPackage(file: string, toImport: var HashSet[string]): ProtoNode =
number: parseInt(fieldValue),
protoType: fieldType,
name: fieldName)))
msg <- ["message"] * >ident * ['{'] * *(typedecl | mapfield | oneof2 | msgfield | reserved | extensions) * ['}']:
# protobuf2
groupfield <- ?multiple * >["group"] * >ident * ['='] * >int * msgbody:
let fields = ps.fields.extract($0)
ps.messages.add(($0, ProtoNode(
messageName: ($2).text,
kind: Message,
nested: ps.messages.extract($0),
definedEnums: ps.enums.extract($0),
reserved: ps.reservedBlocks.extract($0),
fields: fields
)))
let
fieldType = ($2).text
fieldName = ($2).text
fieldValue = ($3).text
let node = ProtoNode(
kind: Field,
number: parseInt(fieldValue),
protoType: fieldType,
name: fieldName)
if @1 != @2:
node.presence = parseEnum[Presence](($0).text.toUpper)
ps.fields.add (($0, node))
extend2 <- ["extend"] * >ident * msgbody:
let fields = ps.fields.extract($0)
ps.messages.add(($0, ProtoNode(
extending: ($1).text,
kind: Extend,
extendedFields: fields
)))
msgbody <- ['{'] * *(typedecl | mapfield | oneof2 | msgfield | reserved | extensions | groupfield | option | extend2) * ['}']
msg <- ["message"] * >ident * msgbody:
let fields = ps.fields.extract($0)
ps.messages.add(($0, ProtoNode(
messageName: ($1).text,
@ -197,6 +246,7 @@ proc parseProtoPackage(file: string, toImport: var HashSet[string]): ProtoNode =
fields: fields
)))
enumfield <- >ident * ['='] * >int * [';']:
let
fieldName = ($1).text
@ -212,17 +262,21 @@ proc parseProtoPackage(file: string, toImport: var HashSet[string]): ProtoNode =
values: ps.fields.extract($0)
)))
typedecl <- (msg | enumdecl)
onething <- (pkg | option | syntax | impor | typedecl)
onething <- (pkg | option | syntax | impor | typedecl | extend2)
g <- +onething
var state = ParseState(currentPackage: ProtoNode(kind: Package))
let match = parser.match(tokens, state)
if match.matchLen != tokens.len:
var msg = "Failed to parse: '" & tokens[match.matchMax].text & "'\n"
let
minToShow = max(0, match.matchMax - 2)
maxToShow = min(tokens.len, match.matchMax + 2)
msg &= tokens[minToShow .. maxToShow].mapIt(it.text).join(" ")
tokenPos = tokens[match.matchMax].filePos
line = fileContent[0..<tokenPos].count('\n') + 1
var msg = filename & ":" & $line & " Failed to parse: '" & tokens[match.matchMax].text & "'\n"
var
minToShow = fileContent.rfind('\n', 0, tokenPos) + 1
maxToShow = fileContent.find('\n', tokenPos)
if maxToShow == -1: maxToShow = msg.len
msg &= fileContent[minToShow ..< maxToShow]
raise newException(CatchableError, msg)
result = state.currentPackage

View File

@ -1,12 +1,75 @@
import os
import os, algorithm, strutils, tables
import macros
import stew/shims/macros as stewmacros
import decldef
export decldef
export decldef, tables
import proto_parser
#Exported for the tests.
proc protoToTypesInternal*(filepath: string): NimNode =
proc getTypeAndPragma(strVal: string): (NimNode, NimNode) =
result[0] = ident(strVal.split('.')[^1]) # TODO: Find a better way to handle namespaces
case strVal:
of "double":
result[0] = ident("float64")
of "int32":
result[1] = ident("pint")
of "int64":
result[1] = ident("pint")
of "uint32":
result[1] = ident("pint")
of "uint64":
result[1] = ident("pint")
of "sint32":
result[1] = ident("sint")
result[0] = ident("int32")
of "sint64":
result[1] = ident("sint")
result[0] = ident("int64")
of "fixed32":
result[1] = ident("fixed")
result[0] = ident("uint32")
of "fixed64":
result[1] = ident("fixed")
result[0] = ident("uint64")
of "sfixed32":
result[1] = ident("fixed")
result[0] = ident("int32")
of "sfixed64":
result[1] = ident("fixed")
result[0] = ident("int64")
of "bytes":
result[0] = newNimNode(nnkBracketExpr).add(
ident("seq"),
ident("byte")
)
proc getMessage(name: string, messages: seq[ProtoNode]): ProtoNode =
for msg in messages:
if msg.kind == ProtoType.Extend:
continue
if name == msg.messageName:
return msg
let res = name.getMessage(msg.nested)
if not res.isNil():
return res
return nil
proc isNested(base: string, currentName: string, messages: seq[ProtoNode]): bool =
let msg = currentName.getMessage(messages)
if msg.isNil():
return false
for field in msg.fields:
if field.kind == ProtoType.Field:
if field.presence == Repeated: continue
if base == field.protoType or base.isNested(field.protoType, messages):
return true
elif field.kind == ProtoType.Oneof:
for f in field.oneof:
if f.presence == Repeated: continue
if base == f.protoType or base.isNested(f.protoType, messages):
return true
# Exported for the tests.
proc protoToTypesInternal*(filepath: string): NimNode {.compileTime.} =
var
packages: seq[ProtoNode] = parseProtobuf(filepath).packages
queue: seq[ProtoNode] = @[]
@ -23,21 +86,24 @@ proc protoToTypesInternal*(filepath: string): NimNode =
name: string
value: NimNode
if next.kind == ProtoType.Enum:
# TODO: allow_alias
var alreadySeen: seq[int] = @[]
name = next.enumName
value = newNimNode(nnkEnumTy).add(newEmptyNode())
for enumField in next.values:
for enumField in next.values.sortedByIt(it.num):
if enumField.num in alreadySeen:
continue
alreadySeen.add(enumField.num)
value.add(newNimNode(nnkEnumFieldDef).add(
ident(enumField.fieldName),
newIntLitNode(enumField.num)
))
else:
if next.kind == ProtoType.Extend:
continue
if (next.definedEnums.len != 0) or (next.nested.len != 0):
queue.add(next)
for nestee in (next.definedEnums & next.nested):
queue.add(nestee)
next.definedEnums = @[]
next.nested = @[]
continue
name = next.messageName
value = newNimNode(nnkObjectTy).add(
@ -45,8 +111,16 @@ proc protoToTypesInternal*(filepath: string): NimNode =
newEmptyNode(),
newNimNode(nnkRecList)
)
var fieldsQueue: seq[ProtoNode] = @[]
for field in next.fields:
fieldsQueue.add(field)
while fieldsQueue.len != 0:
let field = fieldsQueue.pop()
if field.kind == Oneof:
# TODO: ATM the oneof is ignored. Find a way to make it work
for f in field.oneof:
fieldsQueue.add(f)
continue
value[2].add(newNimNode(nnkIdentDefs).add(
newNimNode(nnkPragmaExpr).add(
newNimNode(nnkPostfix).add(
@ -64,69 +138,49 @@ proc protoToTypesInternal*(filepath: string): NimNode =
newEmptyNode()
))
var repeated: int = 0
var isReference = false
for parsed in packages:
if next.messageName.isNested(field.protoType, parsed.messages):
isReference = true
break
if value[2][^1][1].strVal.startsWith("map<"):
var matches = value[2][^1][1].strVal.split({'<', '>', ','})
let (typ1, _) = getTypeAndPragma(matches[1])
let (typ2, _) = getTypeAndPragma(matches[2])
value[2][^1][1] = nnkBracketExpr.newTree(newIdentNode("Table"), typ1, typ2)
else:
let (typ, pragma) = getTypeAndPragma(value[2][^1][1].strVal)
value[2][^1][1] = typ
if not pragma.isNil():
value[2][^1][0][1].add(pragma)
if field.presence == Repeated:
repeated = 1
case value[2][^1][1].strVal:
of "double":
value[2][^1][0][1].add(ident("pfloat64"))
value[2][^1][1] = ident("float32")
of "float32":
value[2][^1][0][1].add(ident("pfloat32"))
of "int32":
value[2][^1][0][1].add(ident("pint"))
of "int64":
value[2][^1][0][1].add(ident("pint"))
of "uint32":
value[2][^1][0][1].add(ident("pint"))
of "uint64":
value[2][^1][0][1].add(ident("pint"))
of "sint32":
value[2][^1][0][1].add(ident("sint"))
value[2][^1][1] = ident("int32")
of "sint64":
value[2][^1][0][1].add(ident("sint"))
value[2][^1][1] = ident("int64")
of "fixed32":
value[2][^1][0][1].add(ident("fixed"))
value[2][^1][1] = ident("uint32")
of "fixed64":
value[2][^1][0][1].add(ident("fixed"))
value[2][^1][1] = ident("uint64")
of "sfixed32":
value[2][^1][0][1].add(ident("fixed"))
value[2][^1][1] = ident("int32")
of "sfixed64":
value[2][^1][0][1].add(ident("fixed"))
value[2][^1][1] = ident("int64")
of "bool":
discard
of "string":
discard
of "bytes":
repeated += 1
value[2][^1][1] = ident("byte")
for _ in 0 ..< repeated:
value[2][^1][1] = newNimNode(nnkBracketExpr).add(
ident("seq"),
value[2][^1][1]
)
elif isReference:
value[2][^1][1] = newNimNode(nnkRefTy).add(value[2][^1][1])
if value[2].len == 0:
value[2] = newEmptyNode()
result.add(
newNimNode(nnkTypeDef).add(
newNimNode(nnkPragmaExpr).add(
newNimNode(nnkPostfix).add(ident("*"), ident(name)),
newNimNode(nnkPragma).add(ident("proto3"))
if next.kind == ProtoType.Enum:
newNimNode(nnkPragma).add(ident("pure"), ident("proto3"))
else:
newNimNode(nnkPragma).add(ident("proto3"))
),
newEmptyNode(),
value
)
)
when defined(LogGeneratedTypes):
result.storeMacroResult(true)
macro protoToTypes*(filepath: static[string]): untyped =
result = protoToTypesInternal(filepath)

View File

@ -1,6 +1,6 @@
#Variables needed by the Reader and Writer which should NOT be exported outside of this library.
import std/[options, sets]
import std/[options, sets, tables]
import stew/shims/macros
#Depending on the situation, one of these two are used.
#Sometimes, one works where the other doesn't.
@ -115,12 +115,16 @@ template protoType*(InnerType, RootType, FieldType: untyped, fieldName: untyped)
type InnerType = pstring
elif FlatType is seq[byte]:
type InnerType = pbytes
elif FlatType is object:
elif FlatType is enum:
type InnerType = penum
elif FlatType is object or FlatType is ref:
type InnerType = FieldType
else:
type InnerType = UnsupportedType[FieldType, RootType, fieldName]
template elementType[T](_: type seq[T]): type = typeof(T)
template elementTypeKey[K, V](_: type Table[K, V]): type = typeof(K)
template elementTypeVal[K, V](_: type Table[K, V]): type = typeof(V)
func verifySerializable*[T](ty: typedesc[T]) {.compileTime.} =
type FlatType = flatType(default(T))
@ -128,7 +132,18 @@ func verifySerializable*[T](ty: typedesc[T]) {.compileTime.} =
{.fatal: $T & ": Serializing a number requires specifying the amount of bits via the type.".}
elif FlatType is seq:
when FlatType isnot seq[byte]:
verifySerializable(elementType(FlatType))
when defined(ConformanceTest):
return # TODO make it work in case of recursivity
# type List = object (value: Value)
# type Value = object (list: List)
else:
verifySerializable(elementType(FlatType))
elif FlatType is Table and defined(ConformanceTest):
return # TODO make it work in case of recursivity
# type Struct = object (map: Table[..., Value])
# type Value = object (struct: Struct)
# verifySerializable(elementTypeKey(FlatType))
# verifySerializable(elementTypeVal(FlatType))
elif FlatType is object and T isnot PBOption:
var
inst: T
@ -138,7 +153,7 @@ func verifySerializable*[T](ty: typedesc[T]) {.compileTime.} =
isProto2 = T.isProto2()
isProto3 = T.isProto3()
when isProto2 == isProto3:
{.fatal: $T & ": missing {.proto2.} or {.proto3}".}
{.fatal: $T & ": missing {.proto2.} or {.proto3.}".}
enumInstanceSerializedFields(inst, fieldName, fieldVar):
when isProto2 and not T.isRequired(fieldName):

View File

@ -1,8 +1,9 @@
#Parses the Protobuf binary wire protocol into the specified type.
import
std/[typetraits, sets],
std/[typetraits, sets, tables],
stew/assign2,
stew/objects,
stew/shims/macros,
faststreams/inputs,
serialization,
@ -22,7 +23,7 @@ template requireKind(header: FieldHeader, expected: WireKind) =
msg: "Unexpected data kind " & $(header.number()) & ": " & $header.kind() &
", exprected " & $expected)
proc readFieldInto[T: object](
proc readFieldInto[T: object and not Table](
stream: InputStream,
value: var T,
header: FieldHeader,
@ -42,7 +43,54 @@ proc readFieldInto[T: object](
raise (ref ValueError)(msg: "not enough bytes")
memoryInput(tmp).readValueInternal(value)
proc readFieldInto[T: not object and (seq[byte] or not seq)](
when defined(ConformanceTest):
proc readFieldInto[T: enum](
stream: InputStream,
value: var T,
header: FieldHeader,
ProtoType: type
) =
# TODO: This function doesn't work for proto2 edge cases. Make it work
when 0 notin T and T.isProto3():
{.fatal: $T & " definition must contain a constant that maps to zero".}
header.requireKind(WireKind.Varint)
let enumValue = stream.readValue(ProtoType)
if not checkedEnumAssign(value, enumValue.int32):
discard checkedEnumAssign(value, 0)
proc readFieldInto[K, V](
stream: InputStream,
value: var Table[K, V],
header: FieldHeader,
ProtoType: type
) =
# I know it's ugly, but I cannot find a clean way to do it
# ... And nobody cares about map
when K is SomePBInt and V is SomePBInt:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1, pint.}: K
value {.fieldNumber: 2, pint.}: V
elif K is SomePBInt:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1, pint.}: K
value {.fieldNumber: 2.}: V
elif V is SomePBInt:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1.}: K
value {.fieldNumber: 2, pint.}: V
else:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1.}: K
value {.fieldNumber: 2.}: V
var tmp = default(TableObject)
stream.readFieldInto(tmp, header, ProtoType)
value[tmp.key] = tmp.value
proc readFieldInto[T: not object and not enum and (seq[byte] or not seq)](
stream: InputStream,
value: var T,
header: FieldHeader,
@ -98,8 +146,7 @@ proc readFieldPackedInto[T](
elif ProtoType is SomeFixed32:
WireKind.Fixed32
else:
static: doAssert ProtoType is SomeFixed64
ProtoType.SomeFixed64
WireKind.Fixed64
inner.readFieldInto(value[^1], FieldHeader.init(header.number, kind), ProtoType)
@ -137,6 +184,9 @@ proc readValueInternal[T: object](stream: InputStream, value: var T, silent: boo
stream.readFieldPackedInto(fieldVar, header, ProtoType)
else:
stream.readFieldInto(fieldVar, header, ProtoType)
elif ProtoType is ref and defined(ConformanceTest):
fieldVar = new ProtoType
stream.readFieldInto(fieldVar[], header, ProtoType)
else:
stream.readFieldInto(fieldVar, header, ProtoType)

View File

@ -1,8 +1,9 @@
#Writes the specified type into a buffer using the Protobuf binary wire format.
import
std/typetraits,
std/[typetraits, tables],
stew/shims/macros,
stew/objects,
faststreams/outputs,
serialization,
"."/[codec, internal, types]
@ -28,7 +29,7 @@ proc writeField[T: object and not PBOption](
stream: OutputStream, fieldNum: int, fieldVal: T, ProtoType: type) =
stream.writeField(fieldNum, fieldVal)
proc writeField[T: not object](
proc writeField[T: not object and not enum](
stream: OutputStream, fieldNum: int, fieldVal: T, ProtoType: type) =
stream.writeField(fieldNum, ProtoType(fieldVal))
@ -67,6 +68,43 @@ proc writeFieldPacked*[T: not byte, ProtoType: SomePrimitive](
for value in values:
output.write(toBytes(ProtoType(value)))
when defined(ConformanceTest):
proc writeField[T: enum](
stream: OutputStream, fieldNum: int, fieldVal: T, ProtoType: type) =
when 0 notin T:
{.fatal: $T & " definition must contain a constant that maps to zero".}
stream.writeField(fieldNum, pint32(fieldVal.ord()))
proc writeField*[K, V](
stream: OutputStream,
fieldNum: int,
value: Table[K, V],
ProtoType: type
) =
when K is SomePBInt and V is SomePBInt:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1, pint.}: K
value {.fieldNumber: 2, pint.}: V
elif K is SomePBInt:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1, pint.}: K
value {.fieldNumber: 2.}: V
elif V is SomePBInt:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1.}: K
value {.fieldNumber: 2, pint.}: V
else:
type
TableObject {.proto3.} = object
key {.fieldNumber: 1.}: K
value {.fieldNumber: 2.}: V
for k, v in value.pairs():
let tmp = TableObject(key: k, value: v)
stream.writeField(fieldNum, tmp, ProtoType)
proc writeValue*[T: object](stream: OutputStream, value: T) =
const
isProto2: bool = T.isProto2()
@ -94,6 +132,9 @@ proc writeValue*[T: object](stream: OutputStream, value: T) =
elif FlatType is object:
# TODO avoid writing empty objects in proto3
stream.writeField(fieldNum, fieldVal, ProtoType)
elif FlatType is ref and defined(ConformanceTest):
if not fieldVal.isNil():
stream.writeField(fieldNum, fieldVal[], ProtoType)
else:
when isProto2:
stream.writeField(fieldNum, fieldVal, ProtoType)

View File

@ -0,0 +1,180 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package conformance;
option java_package = "com.google.protobuf.conformance";
option objc_class_prefix = "Conformance";
// This defines the conformance testing protocol. This protocol exists between
// the conformance test suite itself and the code being tested. For each test,
// the suite will send a ConformanceRequest message and expect a
// ConformanceResponse message.
//
// You can either run the tests in two different ways:
//
// 1. in-process (using the interface in conformance_test.h).
//
// 2. as a sub-process communicating over a pipe. Information about how to
// do this is in conformance_test_runner.cc.
//
// Pros/cons of the two approaches:
//
// - running as a sub-process is much simpler for languages other than C/C++.
//
// - running as a sub-process may be more tricky in unusual environments like
// iOS apps, where fork/stdin/stdout are not available.
enum WireFormat {
UNSPECIFIED = 0;
PROTOBUF = 1;
JSON = 2;
JSPB = 3; // Only used inside Google. Opensource testees just skip it.
TEXT_FORMAT = 4;
}
enum TestCategory {
UNSPECIFIED_TEST = 0;
BINARY_TEST = 1; // Test binary wire format.
JSON_TEST = 2; // Test json wire format.
// Similar to JSON_TEST. However, during parsing json, testee should ignore
// unknown fields. This feature is optional. Each implementation can decide
// whether to support it. See
// https://developers.google.com/protocol-buffers/docs/proto3#json_options
// for more detail.
JSON_IGNORE_UNKNOWN_PARSING_TEST = 3;
// Test jspb wire format. Only used inside Google. Opensource testees just
// skip it.
JSPB_TEST = 4;
// Test text format. For cpp, java and python, testees can already deal with
// this type. Testees of other languages can simply skip it.
TEXT_FORMAT_TEST = 5;
}
// The conformance runner will request a list of failures as the first request.
// This will be known by message_type == "conformance.FailureSet", a conformance
// test should return a serialized FailureSet in protobuf_payload.
message FailureSet {
repeated string failure = 1;
}
// Represents a single test case's input. The testee should:
//
// 1. parse this proto (which should always succeed)
// 2. parse the protobuf or JSON payload in "payload" (which may fail)
// 3. if the parse succeeded, serialize the message in the requested format.
message ConformanceRequest {
// The payload (whether protobuf of JSON) is always for a
// protobuf_test_messages.proto3.TestAllTypes proto (as defined in
// src/google/protobuf/proto3_test_messages.proto).
oneof payload {
bytes protobuf_payload = 1;
string json_payload = 2;
// Only used inside Google. Opensource testees just skip it.
string jspb_payload = 7;
string text_payload = 8;
}
// Which format should the testee serialize its message to?
WireFormat requested_output_format = 3;
// The full name for the test message to use; for the moment, either:
// protobuf_test_messages.proto3.TestAllTypesProto3 or
// protobuf_test_messages.google.protobuf.TestAllTypesProto2.
string message_type = 4;
// Each test is given a specific test category. Some category may need
// specific support in testee programs. Refer to the definition of
// TestCategory for more information.
TestCategory test_category = 5;
// Specify details for how to encode jspb.
JspbEncodingConfig jspb_encoding_options = 6;
// This can be used in json and text format. If true, testee should print
// unknown fields instead of ignore. This feature is optional.
bool print_unknown_fields = 9;
}
// Represents a single test case's output.
message ConformanceResponse {
oneof result {
// This string should be set to indicate parsing failed. The string can
// provide more information about the parse error if it is available.
//
// Setting this string does not necessarily mean the testee failed the
// test. Some of the test cases are intentionally invalid input.
string parse_error = 1;
// If the input was successfully parsed but errors occurred when
// serializing it to the requested output format, set the error message in
// this field.
string serialize_error = 6;
// This should be set if the test program timed out. The string should
// provide more information about what the child process was doing when it
// was killed.
string timeout_error = 9;
// This should be set if some other error occurred. This will always
// indicate that the test failed. The string can provide more information
// about the failure.
string runtime_error = 2;
// If the input was successfully parsed and the requested output was
// protobuf, serialize it to protobuf and set it in this field.
bytes protobuf_payload = 3;
// If the input was successfully parsed and the requested output was JSON,
// serialize to JSON and set it in this field.
string json_payload = 4;
// For when the testee skipped the test, likely because a certain feature
// wasn't supported, like JSON input/output.
string skipped = 5;
// If the input was successfully parsed and the requested output was JSPB,
// serialize to JSPB and set it in this field. JSPB is only used inside
// Google. Opensource testees can just skip it.
string jspb_payload = 7;
// If the input was successfully parsed and the requested output was
// TEXT_FORMAT, serialize to TEXT_FORMAT and set it in this field.
string text_payload = 8;
}
}
// Encoding options for jspb format.
message JspbEncodingConfig {
// Encode the value field of Any as jspb array if true, otherwise binary.
bool use_jspb_array_any_format = 1;
}

View File

@ -0,0 +1,57 @@
import os
import ../../protobuf_serialization
import ../../protobuf_serialization/files/type_generator
import stew/byteutils
import_proto3 "conformance.proto"
import test_proto2
import test_proto3
proc readIntLE(): int32 =
if stdin.readBuffer(addr(result), 4) != 4:
raise newException(IOError, "readInt error")
proc writeIntLE(v: int32) =
var value = v
if stdout.writeBuffer(addr(value), 4) != 4:
raise newException(IOError, "writeInt error")
proc doTest() =
let length = readIntLE()
var serializedRequest = newSeq[byte](length)
if stdin.readBuffer(addr(serializedRequest[0]), length) != length:
raise newException(IOError, "IProtobuf./O error")
let request = Protobuf.decode(serializedRequest, ConformanceRequest)
var response = ConformanceResponse()
if request.requested_output_format != WireFormat.PROTOBUF or
request.protobuf_payload.len() == 0:
response.skipped = "skip not protobuf"
else:
try:
if request.message_type == "protobuf_test_messages.proto3.TestAllTypesProto3":
let x = Protobuf.decode(request.protobuf_payload, TestAllTypesProto3)
response.protobuf_payload = Protobuf.encode(x)
elif request.message_type == "protobuf_test_messages.proto2.TestAllTypesProto2":
let x = Protobuf.decode(request.protobuf_payload, TestAllTypesProto2)
response.protobuf_payload = Protobuf.encode(x)
else:
response.skipped = "skip unknown message type: " & request.message_type
except Exception as exc:
response.parse_error = exc.msg
let serializedResponse = Protobuf.encode(response)
writeIntLE(serializedResponse.len().int32)
stdout.write(string.fromBytes(serializedResponse))
stdout.flushFile()
while true:
try:
doTest()
except IOError as exc:
stderr.writeLine(exc.msg)
break

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,303 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Test schema for proto2 messages. This test schema is used by:
//
// - conformance tests
//
// LINT: ALLOW_GROUPS
syntax = "proto2";
package protobuf_test_messages.proto2;
option java_package = "com.google.protobuf_test_messages.proto2";
option objc_class_prefix = "Proto2";
// This is the default, but we specify it here explicitly.
option optimize_for = SPEED;
option cc_enable_arenas = true;
// This proto includes every type of field in both singular and repeated
// forms.
//
// Also, crucially, all messages and enums in this file are eventually
// submessages of this message. So for example, a fuzz test of TestAllTypes
// could trigger bugs that occur in any message type in this file. We verify
// this stays true in a unit test.
message TestAllTypesProto2 {
message NestedMessage {
optional int32 a = 1;
optional TestAllTypesProto2 corecursive = 2;
}
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
NEG = -1; // Intentionally negative.
}
// Singular
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional sint32 optional_sint32 = 5;
optional sint64 optional_sint64 = 6;
optional fixed32 optional_fixed32 = 7;
optional fixed64 optional_fixed64 = 8;
optional sfixed32 optional_sfixed32 = 9;
optional sfixed64 optional_sfixed64 = 10;
optional float optional_float = 11;
optional double optional_double = 12;
optional bool optional_bool = 13;
optional string optional_string = 14;
optional bytes optional_bytes = 15;
optional NestedMessage optional_nested_message = 18;
optional ForeignMessageProto2 optional_foreign_message = 19;
optional NestedEnum optional_nested_enum = 21;
optional ForeignEnumProto2 optional_foreign_enum = 22;
optional string optional_string_piece = 24 [ctype = STRING_PIECE];
optional string optional_cord = 25 [ctype = CORD];
optional TestAllTypesProto2 recursive_message = 27;
// Repeated
repeated int32 repeated_int32 = 31;
repeated int64 repeated_int64 = 32;
repeated uint32 repeated_uint32 = 33;
repeated uint64 repeated_uint64 = 34;
repeated sint32 repeated_sint32 = 35;
repeated sint64 repeated_sint64 = 36;
repeated fixed32 repeated_fixed32 = 37;
repeated fixed64 repeated_fixed64 = 38;
repeated sfixed32 repeated_sfixed32 = 39;
repeated sfixed64 repeated_sfixed64 = 40;
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
repeated string repeated_string = 44;
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated ForeignMessageProto2 repeated_foreign_message = 49;
repeated NestedEnum repeated_nested_enum = 51;
repeated ForeignEnumProto2 repeated_foreign_enum = 52;
repeated string repeated_string_piece = 54 [ctype = STRING_PIECE];
repeated string repeated_cord = 55 [ctype = CORD];
// Packed
repeated int32 packed_int32 = 75 [packed = true];
repeated int64 packed_int64 = 76 [packed = true];
repeated uint32 packed_uint32 = 77 [packed = true];
repeated uint64 packed_uint64 = 78 [packed = true];
repeated sint32 packed_sint32 = 79 [packed = true];
repeated sint64 packed_sint64 = 80 [packed = true];
repeated fixed32 packed_fixed32 = 81 [packed = true];
repeated fixed64 packed_fixed64 = 82 [packed = true];
repeated sfixed32 packed_sfixed32 = 83 [packed = true];
repeated sfixed64 packed_sfixed64 = 84 [packed = true];
repeated float packed_float = 85 [packed = true];
repeated double packed_double = 86 [packed = true];
repeated bool packed_bool = 87 [packed = true];
repeated NestedEnum packed_nested_enum = 88 [packed = true];
// Unpacked
repeated int32 unpacked_int32 = 89 [packed = false];
repeated int64 unpacked_int64 = 90 [packed = false];
repeated uint32 unpacked_uint32 = 91 [packed = false];
repeated uint64 unpacked_uint64 = 92 [packed = false];
repeated sint32 unpacked_sint32 = 93 [packed = false];
repeated sint64 unpacked_sint64 = 94 [packed = false];
repeated fixed32 unpacked_fixed32 = 95 [packed = false];
repeated fixed64 unpacked_fixed64 = 96 [packed = false];
repeated sfixed32 unpacked_sfixed32 = 97 [packed = false];
repeated sfixed64 unpacked_sfixed64 = 98 [packed = false];
repeated float unpacked_float = 99 [packed = false];
repeated double unpacked_double = 100 [packed = false];
repeated bool unpacked_bool = 101 [packed = false];
repeated NestedEnum unpacked_nested_enum = 102 [packed = false];
// Map
map<int32, int32> map_int32_int32 = 56;
map<int64, int64> map_int64_int64 = 57;
map<uint32, uint32> map_uint32_uint32 = 58;
map<uint64, uint64> map_uint64_uint64 = 59;
map<sint32, sint32> map_sint32_sint32 = 60;
map<sint64, sint64> map_sint64_sint64 = 61;
map<fixed32, fixed32> map_fixed32_fixed32 = 62;
map<fixed64, fixed64> map_fixed64_fixed64 = 63;
map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;
map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;
map<int32, float> map_int32_float = 66;
map<int32, double> map_int32_double = 67;
map<bool, bool> map_bool_bool = 68;
map<string, string> map_string_string = 69;
map<string, bytes> map_string_bytes = 70;
map<string, NestedMessage> map_string_nested_message = 71;
map<string, ForeignMessageProto2> map_string_foreign_message = 72;
map<string, NestedEnum> map_string_nested_enum = 73;
map<string, ForeignEnumProto2> map_string_foreign_enum = 74;
oneof oneof_field {
uint32 oneof_uint32 = 111;
NestedMessage oneof_nested_message = 112;
string oneof_string = 113;
bytes oneof_bytes = 114;
bool oneof_bool = 115;
uint64 oneof_uint64 = 116;
float oneof_float = 117;
double oneof_double = 118;
NestedEnum oneof_enum = 119;
}
// extensions
extensions 120 to 200;
// groups
optional group Data = 201 {
optional int32 group_int32 = 202;
optional uint32 group_uint32 = 203;
}
// default values
optional int32 default_int32 = 241 [default = -123456789];
optional int64 default_int64 = 242 [default = -9123456789123456789];
optional uint32 default_uint32 = 243 [default = 2123456789];
optional uint64 default_uint64 = 244 [default = 10123456789123456789];
optional sint32 default_sint32 = 245 [default = -123456789];
optional sint64 default_sint64 = 246 [default = -9123456789123456789];
optional fixed32 default_fixed32 = 247 [default = 2123456789];
optional fixed64 default_fixed64 = 248 [default = 10123456789123456789];
optional sfixed32 default_sfixed32 = 249 [default = -123456789];
optional sfixed64 default_sfixed64 = 250 [default = -9123456789123456789];
optional float default_float = 251 [default = 9e9];
optional double default_double = 252 [default = 7e22];
optional bool default_bool = 253 [default = true];
optional string default_string = 254 [default = "Rosebud"];
optional bytes default_bytes = 255 [default = "joshua"];
// Test field-name-to-JSON-name convention.
// (protobuf says names can be any valid C/C++ identifier.)
optional int32 fieldname1 = 401;
optional int32 field_name2 = 402;
optional int32 _field_name3 = 403;
optional int32 field__name4_ = 404;
optional int32 field0name5 = 405;
optional int32 field_0_name6 = 406;
optional int32 fieldName7 = 407;
optional int32 FieldName8 = 408;
optional int32 field_Name9 = 409;
optional int32 Field_Name10 = 410;
optional int32 FIELD_NAME11 = 411;
optional int32 FIELD_name12 = 412;
optional int32 __field_name13 = 413;
optional int32 __Field_name14 = 414;
optional int32 field__name15 = 415;
optional int32 field__Name16 = 416;
optional int32 field_name17__ = 417;
optional int32 Field_name18__ = 418;
// Reserved for unknown fields test.
reserved 1000 to 9999;
// message_set test case.
message MessageSetCorrect {
option message_set_wire_format = true;
extensions 4 to max;
}
message MessageSetCorrectExtension1 {
extend MessageSetCorrect {
optional MessageSetCorrectExtension1 message_set_extension = 1547769;
}
optional string str = 25;
}
message MessageSetCorrectExtension2 {
extend MessageSetCorrect {
optional MessageSetCorrectExtension2 message_set_extension = 4135312;
}
optional int32 i = 9;
}
}
message ForeignMessageProto2 {
optional int32 c = 1;
}
enum ForeignEnumProto2 {
FOREIGN_FOO = 0;
FOREIGN_BAR = 1;
FOREIGN_BAZ = 2;
}
extend TestAllTypesProto2 {
optional int32 extension_int32 = 120;
}
message UnknownToTestAllTypes {
optional int32 optional_int32 = 1001;
optional string optional_string = 1002;
optional ForeignMessageProto2 nested_message = 1003;
optional group OptionalGroup = 1004 {
optional int32 a = 1;
}
optional bool optional_bool = 1006;
repeated int32 repeated_int32 = 1011;
}
message NullHypothesisProto2 {}
message EnumOnlyProto2 {
enum Bool {
kFalse = 0;
kTrue = 1;
}
}
message OneStringProto2 {
optional string data = 1;
}
message ProtoWithKeywords {
optional int32 inline = 1;
optional string concept = 2;
repeated string requires = 3;
}

View File

@ -0,0 +1,288 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Test schema for proto3 messages. This test schema is used by:
//
// - benchmarks
// - fuzz tests
// - conformance tests
//
syntax = "proto3";
package protobuf_test_messages.proto3;
option java_package = "com.google.protobuf_test_messages.proto3";
option objc_class_prefix = "Proto3";
// This is the default, but we specify it here explicitly.
option optimize_for = SPEED;
import "google/protobuf/any.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
option cc_enable_arenas = true;
// This proto includes every type of field in both singular and repeated
// forms.
//
// Also, crucially, all messages and enums in this file are eventually
// submessages of this message. So for example, a fuzz test of TestAllTypes
// could trigger bugs that occur in any message type in this file. We verify
// this stays true in a unit test.
message TestAllTypesProto3 {
message NestedMessage {
int32 a = 1;
TestAllTypesProto3 corecursive = 2;
}
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
NEG = -1; // Intentionally negative.
}
enum AliasedEnum {
option allow_alias = true;
ALIAS_FOO = 0;
ALIAS_BAR = 1;
ALIAS_BAZ = 2;
MOO = 2;
moo = 2;
bAz = 2;
}
// Singular
int32 optional_int32 = 1;
int64 optional_int64 = 2;
uint32 optional_uint32 = 3;
uint64 optional_uint64 = 4;
sint32 optional_sint32 = 5;
sint64 optional_sint64 = 6;
fixed32 optional_fixed32 = 7;
fixed64 optional_fixed64 = 8;
sfixed32 optional_sfixed32 = 9;
sfixed64 optional_sfixed64 = 10;
float optional_float = 11;
double optional_double = 12;
bool optional_bool = 13;
string optional_string = 14;
bytes optional_bytes = 15;
NestedMessage optional_nested_message = 18;
ForeignMessage optional_foreign_message = 19;
NestedEnum optional_nested_enum = 21;
ForeignEnum optional_foreign_enum = 22;
AliasedEnum optional_aliased_enum = 23;
string optional_string_piece = 24 [ctype = STRING_PIECE];
string optional_cord = 25 [ctype = CORD];
TestAllTypesProto3 recursive_message = 27;
// Repeated
repeated int32 repeated_int32 = 31;
repeated int64 repeated_int64 = 32;
repeated uint32 repeated_uint32 = 33;
repeated uint64 repeated_uint64 = 34;
repeated sint32 repeated_sint32 = 35;
repeated sint64 repeated_sint64 = 36;
repeated fixed32 repeated_fixed32 = 37;
repeated fixed64 repeated_fixed64 = 38;
repeated sfixed32 repeated_sfixed32 = 39;
repeated sfixed64 repeated_sfixed64 = 40;
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
repeated string repeated_string = 44;
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated ForeignMessage repeated_foreign_message = 49;
repeated NestedEnum repeated_nested_enum = 51;
repeated ForeignEnum repeated_foreign_enum = 52;
repeated string repeated_string_piece = 54 [ctype = STRING_PIECE];
repeated string repeated_cord = 55 [ctype = CORD];
// Packed
repeated int32 packed_int32 = 75 [packed = true];
repeated int64 packed_int64 = 76 [packed = true];
repeated uint32 packed_uint32 = 77 [packed = true];
repeated uint64 packed_uint64 = 78 [packed = true];
repeated sint32 packed_sint32 = 79 [packed = true];
repeated sint64 packed_sint64 = 80 [packed = true];
repeated fixed32 packed_fixed32 = 81 [packed = true];
repeated fixed64 packed_fixed64 = 82 [packed = true];
repeated sfixed32 packed_sfixed32 = 83 [packed = true];
repeated sfixed64 packed_sfixed64 = 84 [packed = true];
repeated float packed_float = 85 [packed = true];
repeated double packed_double = 86 [packed = true];
repeated bool packed_bool = 87 [packed = true];
repeated NestedEnum packed_nested_enum = 88 [packed = true];
// Unpacked
repeated int32 unpacked_int32 = 89 [packed = false];
repeated int64 unpacked_int64 = 90 [packed = false];
repeated uint32 unpacked_uint32 = 91 [packed = false];
repeated uint64 unpacked_uint64 = 92 [packed = false];
repeated sint32 unpacked_sint32 = 93 [packed = false];
repeated sint64 unpacked_sint64 = 94 [packed = false];
repeated fixed32 unpacked_fixed32 = 95 [packed = false];
repeated fixed64 unpacked_fixed64 = 96 [packed = false];
repeated sfixed32 unpacked_sfixed32 = 97 [packed = false];
repeated sfixed64 unpacked_sfixed64 = 98 [packed = false];
repeated float unpacked_float = 99 [packed = false];
repeated double unpacked_double = 100 [packed = false];
repeated bool unpacked_bool = 101 [packed = false];
repeated NestedEnum unpacked_nested_enum = 102 [packed = false];
// Map
map<int32, int32> map_int32_int32 = 56;
map<int64, int64> map_int64_int64 = 57;
map<uint32, uint32> map_uint32_uint32 = 58;
map<uint64, uint64> map_uint64_uint64 = 59;
map<sint32, sint32> map_sint32_sint32 = 60;
map<sint64, sint64> map_sint64_sint64 = 61;
map<fixed32, fixed32> map_fixed32_fixed32 = 62;
map<fixed64, fixed64> map_fixed64_fixed64 = 63;
map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;
map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;
map<int32, float> map_int32_float = 66;
map<int32, double> map_int32_double = 67;
map<bool, bool> map_bool_bool = 68;
map<string, string> map_string_string = 69;
map<string, bytes> map_string_bytes = 70;
map<string, NestedMessage> map_string_nested_message = 71;
map<string, ForeignMessage> map_string_foreign_message = 72;
map<string, NestedEnum> map_string_nested_enum = 73;
map<string, ForeignEnum> map_string_foreign_enum = 74;
oneof oneof_field {
uint32 oneof_uint32 = 111;
NestedMessage oneof_nested_message = 112;
string oneof_string = 113;
bytes oneof_bytes = 114;
bool oneof_bool = 115;
uint64 oneof_uint64 = 116;
float oneof_float = 117;
double oneof_double = 118;
NestedEnum oneof_enum = 119;
google.protobuf.NullValue oneof_null_value = 120;
}
// Well-known types
google.protobuf.BoolValue optional_bool_wrapper = 201;
google.protobuf.Int32Value optional_int32_wrapper = 202;
google.protobuf.Int64Value optional_int64_wrapper = 203;
google.protobuf.UInt32Value optional_uint32_wrapper = 204;
google.protobuf.UInt64Value optional_uint64_wrapper = 205;
google.protobuf.FloatValue optional_float_wrapper = 206;
google.protobuf.DoubleValue optional_double_wrapper = 207;
google.protobuf.StringValue optional_string_wrapper = 208;
google.protobuf.BytesValue optional_bytes_wrapper = 209;
repeated google.protobuf.BoolValue repeated_bool_wrapper = 211;
repeated google.protobuf.Int32Value repeated_int32_wrapper = 212;
repeated google.protobuf.Int64Value repeated_int64_wrapper = 213;
repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214;
repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215;
repeated google.protobuf.FloatValue repeated_float_wrapper = 216;
repeated google.protobuf.DoubleValue repeated_double_wrapper = 217;
repeated google.protobuf.StringValue repeated_string_wrapper = 218;
repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219;
google.protobuf.Duration optional_duration = 301;
google.protobuf.Timestamp optional_timestamp = 302;
google.protobuf.FieldMask optional_field_mask = 303;
google.protobuf.Struct optional_struct = 304;
google.protobuf.Any optional_any = 305;
google.protobuf.Value optional_value = 306;
google.protobuf.NullValue optional_null_value = 307;
repeated google.protobuf.Duration repeated_duration = 311;
repeated google.protobuf.Timestamp repeated_timestamp = 312;
repeated google.protobuf.FieldMask repeated_fieldmask = 313;
repeated google.protobuf.Struct repeated_struct = 324;
repeated google.protobuf.Any repeated_any = 315;
repeated google.protobuf.Value repeated_value = 316;
repeated google.protobuf.ListValue repeated_list_value = 317;
// Test field-name-to-JSON-name convention.
// (protobuf says names can be any valid C/C++ identifier.)
int32 fieldname1 = 401;
int32 field_name2 = 402;
int32 _field_name3 = 403;
int32 field__name4_ = 404;
int32 field0name5 = 405;
int32 field_0_name6 = 406;
int32 fieldName7 = 407;
int32 FieldName8 = 408;
int32 field_Name9 = 409;
int32 Field_Name10 = 410;
int32 FIELD_NAME11 = 411;
int32 FIELD_name12 = 412;
int32 __field_name13 = 413;
int32 __Field_name14 = 414;
int32 field__name15 = 415;
int32 field__Name16 = 416;
int32 field_name17__ = 417;
int32 Field_name18__ = 418;
// Reserved for testing unknown fields
reserved 501 to 510;
}
message ForeignMessage {
int32 c = 1;
}
enum ForeignEnum {
FOREIGN_FOO = 0;
FOREIGN_BAR = 1;
FOREIGN_BAZ = 2;
}
message NullHypothesisProto3 {}
message EnumOnlyProto3 {
enum Bool {
kFalse = 0;
kTrue = 1;
}
}

View File

@ -0,0 +1,3 @@
import ../../protobuf_serialization
import ../../protobuf_serialization/files/type_generator
import_proto3 "test_messages_proto2.proto"

View File

@ -0,0 +1,3 @@
import ../../protobuf_serialization
import ../../protobuf_serialization/files/type_generator
import_proto3 "test_messages_proto3.proto"

View File

@ -10,23 +10,29 @@ macro test() =
parsed: NimNode = protoToTypesInternal(currentSourcePath.parentDir / "test.proto3")
vector: NimNode = quote do:
type
TestEnum* {.proto3.} = enum
TestEnum* {.pure, proto3.} = enum
UNKNOWN = 0
STARTED = 1
ErrorStatus* {.proto3.} = object
message* {.fieldNumber: 1.}: string
details* {.fieldNumber: 2.}: seq[seq[byte]]
Result* {.proto3.} = object
url* {.fieldNumber: 1.}: string
title* {.fieldNumber: 2.}: string
snippets* {.fieldNumber: 3.}: seq[string]
message* {.fieldNumber: 1.}: string
SearchResponse* {.proto3.} = object
results* {.fieldNumber: 1.}: seq[Result]
Corpus* {.proto3.} = enum
Result* {.proto3.} = object
snippets* {.fieldNumber: 3.}: seq[string]
title* {.fieldNumber: 2.}: string
url* {.fieldNumber: 1.}: string
SearchRequest* {.proto3.} = object
corpus* {.fieldNumber: 4.}: Corpus
result_per_page* {.fieldNumber: 3, pint.}: int32
page_number* {.fieldNumber: 2, pint.}: int32
query* {.fieldNumber: 1.}: string
Corpus* {.pure, proto3.} = enum
UNIVERSAL = 0
WEB = 1
IMAGES = 2
@ -35,12 +41,6 @@ macro test() =
PRODUCTS = 5
VIDEO = 6
SearchRequest* {.proto3.} = object
query* {.fieldNumber: 1.}: string
page_number* {.fieldNumber: 2, pint.}: int32
result_per_page* {.fieldNumber: 3, pint.}: int32
corpus* {.fieldNumber: 4.}: Corpus
Foo* {.proto3.} = object
proc convertFromSym(parent: NimNode, i: int) =

79
tests/test_enum.nim Normal file
View File

@ -0,0 +1,79 @@
import unittest2
import
../protobuf_serialization,
../protobuf_serialization/codec
type
Classic = enum
A1
B1
C1
WithHoles = enum
A2 = -10
B2 = 0
C2 = 10
D2
Limits = enum
A3 = int32.low()
B3 = 0
C3 = int32.high()
ObjClassicP2 {.proto2.} = object
x {.fieldNumber: 1, required.}: Classic
ObjWithHolesP2 {.proto2.} = object
x {.fieldNumber: 1, required.}: WithHoles
ObjLimitsP2 {.proto2.} = object
x {.fieldNumber: 1, required.}: Limits
ObjClassicP3 {.proto3.} = object
x {.fieldNumber: 1.}: Classic
ObjWithHolesP3 {.proto3.} = object
x {.fieldNumber: 1.}: WithHoles
ObjLimitsP3 {.proto3.} = object
x {.fieldNumber: 1.}: Limits
suite "Test Enum Encoding/Decoding":
test "Can encode/decode enum":
for x in @[A1, B1, C1]:
let
objp2 = ObjClassicP2(x: x)
objp3 = ObjClassicP3(x: x)
encodedp2 = Protobuf.encode(objp2)
encodedp3 = Protobuf.encode(objp3)
check Protobuf.decode(encodedp2, ObjClassicP2) == objp2
check Protobuf.decode(encodedp3, ObjClassicP3) == objp3
test "Can encode/decode enum with holes":
for x in @[A2, B2, C2, D2]:
let
objp2 = ObjWithHolesP2(x: x)
objp3 = ObjWithHolesP3(x: x)
encodedp2 = Protobuf.encode(objp2)
encodedp3 = Protobuf.encode(objp3)
check Protobuf.decode(encodedp2, ObjWithHolesP2) == objp2
check Protobuf.decode(encodedp3, ObjWithHolesP3) == objp3
test "Can encode/decode enum limits":
for x in @[A3, B3, C3]:
let
objp2 = ObjLimitsP2(x: x)
objp3 = ObjLimitsP3(x: x)
encodedp2 = Protobuf.encode(objp2)
encodedp3 = Protobuf.encode(objp3)
check Protobuf.decode(encodedp2, ObjLimitsP2) == objp2
check Protobuf.decode(encodedp3, ObjLimitsP3) == objp3
test "Decode out of range enum":
# TODO: Find a way to save the unrecognized value
check:
Protobuf.decode(@[8'u8, 4], ObjWithHolesP2) == ObjWithHolesP2() # Inside the hole
Protobuf.decode(@[8'u8, 4], ObjWithHolesP3) == ObjWithHolesP3() # Inside the hole
Protobuf.decode(@[8'u8, 24], ObjWithHolesP2) == ObjWithHolesP2() # Outside the hole
Protobuf.decode(@[8'u8, 24], ObjWithHolesP3) == ObjWithHolesP3() # Outside the hole

View File

@ -5,6 +5,11 @@ import
../protobuf_serialization/codec
type
# TestEnum = enum
# A1 = 0
# B1 = 1000
# C1 = 1000000
Basic {.proto3.} = object
a {.fieldNumber: 1, pint.}: uint64
b {.fieldNumber: 2.}: string
@ -16,6 +21,7 @@ type
f {.fieldNumber: 3.}: Basic
g {.fieldNumber: 4.}: string
h {.fieldNumber: 5.}: bool
#i {.fieldNumber: 6.}: TestEnum
discard Protobuf.supports(Basic)
discard Protobuf.supports(Wrapped)
@ -72,6 +78,7 @@ suite "Test Object Encoding/Decoding":
writer = memoryOutput()
writer.writeField(3, obj.f)
#writer.writeField(6, penum(obj.i))
writer.writeField(1, sint64(obj.d))
writer.writeField(2, sint64(obj.e))
writer.writeField(5, pbool(obj.h))