commit
d992b8a2a6
348 changed files with 10887 additions and 8662 deletions
@ -1,23 +1,54 @@ |
||||
name: Bazel |
||||
name: Bazel Tests |
||||
|
||||
# Controls when the action will run. |
||||
on: |
||||
# Triggers the workflow on push or pull request events but only for the main branch |
||||
push: |
||||
branches: [main] |
||||
pull_request: |
||||
branches: [main] |
||||
# Allows you to run this workflow manually from the Actions tab |
||||
workflow_dispatch: |
||||
workflow_call: |
||||
inputs: |
||||
safe-checkout: |
||||
required: true |
||||
description: "The SHA key for the commit we want to run over" |
||||
type: string |
||||
|
||||
concurrency: |
||||
# Cancel previous actions from the same PR or branch except 'main' branch. |
||||
# See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info. |
||||
group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}} |
||||
cancel-in-progress: ${{ github.ref_name != 'main' }} |
||||
permissions: |
||||
contents: read |
||||
|
||||
jobs: |
||||
test: |
||||
uses: bazel-contrib/.github/.github/workflows/bazel.yaml@v6 |
||||
with: |
||||
folders: '["examples"]' |
||||
examples: |
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
runner: [ ubuntu, windows, macos ] |
||||
bazelversion: [ '7.1.1' ] |
||||
bzlmod: [true, false ] |
||||
include: |
||||
- runner: ubuntu |
||||
bazelversion: '6.4.0' |
||||
bzlmod: true |
||||
- runner: ubuntu |
||||
bazelversion: '6.4.0' |
||||
bzlmod: false |
||||
runs-on: ${{ matrix.runner }}-latest |
||||
name: Examples ${{ matrix.runner }} ${{ matrix.bazelversion }}${{ matrix.bzlmod && ' (bzlmod)' || '' }} |
||||
steps: |
||||
- name: Checkout pending changes |
||||
uses: protocolbuffers/protobuf-ci/checkout@v3 |
||||
with: |
||||
ref: ${{ inputs.safe-checkout }} |
||||
|
||||
- name: Windows startup flags |
||||
if: runner.os == 'Windows' |
||||
working-directory: examples |
||||
shell: bash |
||||
run: echo "startup --output_user_root=C:/ --windows_enable_symlinks" >> .bazelrc |
||||
|
||||
- name: Configure Bazel version |
||||
working-directory: examples |
||||
shell: bash |
||||
run: echo "${{ matrix.bazelversion }}" > .bazelversion |
||||
|
||||
- name: Run tests |
||||
uses: protocolbuffers/protobuf-ci/bazel@v3 |
||||
with: |
||||
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }} |
||||
bazel-cache: examples |
||||
version: ${{ matrix.bazelversion }} |
||||
bash: cd examples && bazel build //... $BAZEL_FLAGS --enable_bzlmod=${{ matrix.bzlmod }} |
||||
|
@ -0,0 +1,17 @@ |
||||
# Copyright (c) 2009-2021, Google LLC |
||||
# All rights reserved. |
||||
# |
||||
# Use of this source code is governed by a BSD-style |
||||
# license that can be found in the LICENSE file or at |
||||
# https://developers.google.com/open-source/licenses/bsd |
||||
|
||||
"""Temporary alias to repository rule for using Python 3.x headers from the system.""" |
||||
|
||||
load( |
||||
"//python/dist:system_python.bzl", |
||||
_system_python = "system_python", |
||||
) |
||||
|
||||
# TODO: Temporary alias. This is deprecated and to be removed in a future |
||||
# release. Users should now get system_python from protobuf_deps.bzl. |
||||
system_python = _system_python |
@ -1,2 +1,5 @@ |
||||
import common.bazelrc |
||||
|
||||
# Workaround for maximum path length issues |
||||
startup --output_user_root=C:/tmp --windows_enable_symlinks |
||||
common --enable_runfiles |
@ -0,0 +1,20 @@ |
||||
# Simple build tests for compatibility of gencode from previous major versions |
||||
# with the current runtime. |
||||
# |
||||
# To add more test cases in Java, use java_runtime_conformance as below, and add |
||||
# the corresponding http_archive in the WORKSPACE file for the version. |
||||
|
||||
load("//compatibility:runtime_conformance.bzl", "java_runtime_conformance") |
||||
|
||||
# main gencode builds with main runtime as a proof of concept. |
||||
java_runtime_conformance( |
||||
name = "java_conformance_main", |
||||
gencode_version = "main", |
||||
) |
||||
|
||||
# Generates a build_test named "conformance_v3.25.0" |
||||
java_runtime_conformance( |
||||
name = "java_conformance_v3.25.0", |
||||
gencode_version = "3.25.0", |
||||
tags = ["manual"], |
||||
) |
@ -0,0 +1,53 @@ |
||||
"""Provides a rule to generate a conformance test for a given version of the gencode.""" |
||||
|
||||
load("@bazel_skylib//rules:build_test.bzl", "build_test") |
||||
|
||||
def java_runtime_conformance(name, gencode_version, tags = []): |
||||
"""Generates a conformance test for the given version of the runtime. |
||||
|
||||
For example, runtime_conformance("3.19.4") will generate a build test named "conformance_v3.19.4" |
||||
that will fail if the runtime at that version fails to compile the unittest proto. |
||||
|
||||
Args: |
||||
name: The name of the test. |
||||
gencode_version: The version of the runtime to test. |
||||
tags: Any tags to apply to the generated test. |
||||
""" |
||||
|
||||
if gencode_version == "main": |
||||
protoc = "//:protoc" |
||||
else: |
||||
minor = gencode_version[gencode_version.find(".") + 1:] |
||||
protoc = Label("@com_google_protobuf_v%s//:protoc" % minor) |
||||
|
||||
gencode = [ |
||||
"v%s/protobuf_unittest/UnittestProto.java" % gencode_version, |
||||
"v%s/com/google/protobuf/test/UnittestImport.java" % gencode_version, |
||||
"v%s/com/google/protobuf/test/UnittestImportPublic.java" % gencode_version, |
||||
] |
||||
native.genrule( |
||||
name = "unittest_proto_gencode_v" + gencode_version, |
||||
srcs = [ |
||||
"//src/google/protobuf:unittest_proto_srcs", |
||||
], |
||||
outs = gencode, |
||||
cmd = "$(location %s) " % protoc + |
||||
"$(locations //src/google/protobuf:unittest_proto_srcs) " + |
||||
" -Isrc/ --java_out=$(@D)/v%s" % gencode_version, |
||||
tools = [protoc], |
||||
) |
||||
|
||||
conformance_name = "conformance_v" + gencode_version |
||||
conformance_lib_name = conformance_name + "_lib" |
||||
native.java_library( |
||||
name = conformance_lib_name, |
||||
srcs = gencode, |
||||
deps = ["//java/core"], |
||||
tags = tags, |
||||
) |
||||
|
||||
build_test( |
||||
name = name, |
||||
targets = [":" + conformance_lib_name], |
||||
tags = tags, |
||||
) |
@ -1,17 +1,20 @@ |
||||
Recommended.Proto3.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.MessageUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Drop.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.AnyField.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.AnyField.TextFormatOutput |
||||
Recommended.Editions_Proto3.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Editions_Proto3.ProtobufInput.MessageUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Editions_Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Editions_Proto3.ProtobufInput.ScalarUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.MessageUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Drop.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.AnyField.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.AnyField.TextFormatOutput |
||||
|
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Hex |
||||
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Octal |
||||
Required.Proto3.TextFormatInput.AnyField.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.AnyField.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Proto3.TextFormatInput.StringFieldBadUTF8Hex |
||||
Required.Proto3.TextFormatInput.StringFieldBadUTF8Octal |
||||
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Hex |
||||
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Octal |
@ -1,8 +0,0 @@ |
||||
Recommended.Proto3.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.GroupUnknownFields_Print.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.MessageUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.MessageUnknownFields_Print.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Print.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Drop.TextFormatOutput |
||||
Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Print.TextFormatOutput |
@ -1,15 +1,99 @@ |
||||
# This is the list of text format conformance tests that are known to fail right |
||||
# now. |
||||
# TODO: These should be fixed. |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatField_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatField_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_f.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_f.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMinValue_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMinValue_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegative_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegative_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooLarge_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooLarge_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooSmall_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooSmall_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithInt32Max_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithInt32Max_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatField_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatField_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_f.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_f.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMinValue_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMinValue_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegative_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegative_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldTooLarge_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooLarge_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooSmall_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooSmall_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithInt32Max_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithInt32Max_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_f.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_f.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.NegDoubleFieldLargeNegativeExponentParsesAsNegZero.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.NegDoubleFieldLargeNegativeExponentParsesAsNegZero.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.NegFloatFieldLargeNegativeExponentParsesAsNegZero.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.NegFloatFieldLargeNegativeExponentParsesAsNegZero.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_f.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_f.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.NegDoubleFieldLargeNegativeExponentParsesAsNegZero.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.NegDoubleFieldLargeNegativeExponentParsesAsNegZero.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.NegFloatFieldLargeNegativeExponentParsesAsNegZero.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.NegFloatFieldLargeNegativeExponentParsesAsNegZero.TextFormatOutput |
||||
|
@ -1,8 +1,72 @@ |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatField_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatField_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMinValue_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMinValue_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegative_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegative_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooLarge_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooLarge_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooSmall_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooSmall_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithInt32Max_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithInt32Max_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatField_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatField_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMinValue_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMinValue_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegative_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegative_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldTooLarge_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooLarge_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooSmall_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooSmall_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithInt32Max_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithInt32Max_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
|
@ -1,11 +1,75 @@ |
||||
# This is the list of text format conformance tests that are known to fail right |
||||
# now. |
||||
# TODO: These should be fixed. |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatField_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatField_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMaxValue_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMinValue_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldMinValue_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegative_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegative_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNegativeZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooLarge_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooLarge_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooSmall_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldTooSmall_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithInt32Max_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithInt32Max_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldZero_F.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.FloatFieldZero_F.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatField_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatField_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanInt64_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldLargerThanUint64_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMaxValue_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMinValue_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldMinValue_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegative_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegative_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeNoLeadingZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNegativeZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoLeadingZeroWithExponent_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldNoNegativeOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldNoOctal |
||||
Required.Proto3.TextFormatInput.FloatFieldTooLarge_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooLarge_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooSmall_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldTooSmall_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithInt32Max_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithInt32Max_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldWithVeryPreciseNumber_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldZero_F.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.FloatFieldZero_F.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput |
||||
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.TextFormatOutput |
||||
|
Binary file not shown.
@ -0,0 +1,401 @@ |
||||
# Editions: Group Migration Issues |
||||
|
||||
**Authors**: [@mkruskal-google](https://github.com/mkruskal-google) |
||||
|
||||
## Summary |
||||
|
||||
Address some unexpected issues in delimited encoding in edition 2023 before its |
||||
OSS release. |
||||
|
||||
## Background |
||||
|
||||
Joshua Humphries reported some well-timed |
||||
[issues](https://github.com/protocolbuffers/protobuf/issues/16239) discovered |
||||
while experimenting with our early release of Edition 2023. He discovered that |
||||
our new message encoding feature piggybacked a bit too much on the old group |
||||
logic, and actually ended up being virtually useless in general. |
||||
|
||||
None of our testing or migrations caught this because they were heavily focused |
||||
on *preserving* old behavior (which is the primary goal of edition 2023). |
||||
Delimited messages structured exactly like proto2 groups (e.g. message and field |
||||
in the same scope with matching names) continued to work exactly as before, |
||||
making it seem like everything was fine. |
||||
|
||||
All of this is especially problematic in light of *Submessages: In Pursuit of a |
||||
More Perfect Encoding* (not available externally yet), which intends to migrate the |
||||
ecosystem to use delimited encoding everywhere. Releasing a semi-broken feature |
||||
as a migration tool to eliminate a deprecated syntax is one thing, but trying to |
||||
push the ecosystem to it is especially bad. |
||||
|
||||
## Overview |
||||
|
||||
The problems here stem from the fact that before edition 2023, the field and |
||||
type name of group fields was guaranteed to always be unique and intuitive. |
||||
Proto2 splits groups into a synthetic nested message with a type name equivalent |
||||
to the group specification (required to be capitalized), and a field name that's |
||||
fully lowercased. For example, |
||||
|
||||
``` |
||||
optional group MyGroup = 1 { ... } |
||||
``` |
||||
|
||||
would become: |
||||
|
||||
``` |
||||
message MyGroup { ... } |
||||
optional MyGroup mygroup = 1; |
||||
``` |
||||
|
||||
The casing here is very important, since the transformation is irreversible. We |
||||
can't recover the group name from the field name in general, only if the group |
||||
is a single word. |
||||
|
||||
The problem under edition 2023 is that we've removed the generation of |
||||
synchronized synthetic messages from the language. Users now explicitly define |
||||
messages, and any message field can be marked `DELIMITED`. This means that |
||||
anyone assuming that the type and field name are synchronized could now be |
||||
broken. |
||||
|
||||
### Codegen |
||||
|
||||
While using the field name for generated APIs required less special-casing in |
||||
the generators, the field name ends up producing slightly-less-readable APIs for |
||||
multi-word camelcased groups. The result is that we see a fairly random-seeming |
||||
mix in different generators. Using protoc-explorer (not available externally), |
||||
we find the following: |
||||
|
||||
<table> |
||||
<tr> |
||||
<td><strong>Language</strong> |
||||
</td> |
||||
<td><strong>Generated APIs</strong> |
||||
</td> |
||||
<td><strong>Example proto2 getter</strong> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>C++ |
||||
</td> |
||||
<td>field |
||||
</td> |
||||
<td><code>MyGroup mygroup()</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Java (all) |
||||
</td> |
||||
<td>message |
||||
</td> |
||||
<td><code>MyGroup getMyGroup()</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Python |
||||
</td> |
||||
<td>field |
||||
</td> |
||||
<td><code>mygroup</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Go (all) |
||||
</td> |
||||
<td>field |
||||
</td> |
||||
<td><code>GetMygroup() *Foo_MyGroup</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Dart V1 |
||||
</td> |
||||
<td>field/message* |
||||
</td> |
||||
<td><code>get mygroup</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>upb ** |
||||
</td> |
||||
<td>field |
||||
</td> |
||||
<td><code>Foo_mygroup()</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Objective-c |
||||
</td> |
||||
<td>message |
||||
</td> |
||||
<td><code>MyGroup* myGroup</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Swift |
||||
</td> |
||||
<td>message |
||||
</td> |
||||
<td><code>MyGroup myGroup</code> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td>C# |
||||
</td> |
||||
<td>field/message* |
||||
</td> |
||||
<td><code>MyGroup Mygroup</code> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
\* This codegen difference was [caught](cl/611144002) during the implementation |
||||
and intentionally "fixed" in Edition 2023. \ |
||||
\*\* This includes all upb-based runtimes as well (e.g. Ruby, Rust, etc.) \ |
||||
† Extensions use field |
||||
|
||||
In the Dart V1 implementation, we decided to intentionally introduce a behavior |
||||
change on editions upgrades. It was determined that this only affected a handful |
||||
of protos in google3, and could probably be manually fixed as-needed. Java's |
||||
handling changes the story significantly, since over 50% of protos in google3 |
||||
produce generated Java code. Objective-C is also noteworthy since we open-source |
||||
it, and Swift because it's widely used in OSS and we don't own it. |
||||
|
||||
While the editions upgrade is still non-breaking, it means that the generated |
||||
APIs could have very surprising spellings and may not be unique. For example, |
||||
using the same type for two delimited fields in the same containing message will |
||||
create two sets of generated APIs with the same name in some languages! |
||||
|
||||
### Text Format |
||||
|
||||
Our "official" |
||||
[draft specification](https://protobuf.dev/reference/protobuf/textformat-spec/) |
||||
of text-format explicitly states that group messages are encoded by the *message |
||||
name*, rather than the lowercases field name. A group `MyGroup` will be |
||||
serialized as: |
||||
|
||||
``` |
||||
MyGroup { |
||||
... |
||||
} |
||||
``` |
||||
|
||||
In C++, we always serialize the message name and have special handling to only |
||||
accept the message name in parsing. We also have conformance tests locking down |
||||
the positive path here (i.e. using the message name round-trip). The negative |
||||
path (i.e. failing to accept the field name) doesn't have a conformance test, |
||||
but C++/Java/Python all agree and there's no known case that doesn't. |
||||
|
||||
To make things even stranger, for *extensions* (group fields extending other |
||||
messages), we always use the field name for groups. So as far as group |
||||
extensions are concerned, there's no problem for editions. |
||||
|
||||
There are a few problems with non-extension group fields in editions: |
||||
|
||||
* Refactoring the message name will change any text-format output |
||||
* New delimited fields will have unexpected text-format output, that *could* |
||||
conflict with other fields |
||||
* Text parsers will expect the message name, which is surprising and could be |
||||
impossible to specify uniquely |
||||
|
||||
## Recommendation |
||||
|
||||
Clearly the end-state we want is for the field name to be used in all generated |
||||
APIs, and for text-format serialization/parsing. The only questions are: how do |
||||
we get there and can/should we do it in time for the 2023 release in 27.0 next |
||||
month? |
||||
|
||||
We propose a combination of the alternatives listed below. |
||||
[Smooth Extension](#smooth-extension) seems like the best short-term path |
||||
forward to unblock the delimited migration. It *mostly* solves the problem and |
||||
doesn't require any new features. The necessary changes for this approach have |
||||
already been prepared, along with new conformance tests to lock down the |
||||
behavior changes. |
||||
|
||||
[Global Feature](#global-feature) is a good long-term mitigation for tech debt |
||||
we're leaving behind with *Smooth Extension*. Ultimately we would like to remove |
||||
any labeling of fields by their type, and editions provides a good mechanism to |
||||
do this. Alternatively, we could implement [aliases](#aliases) and use that to |
||||
unify this old behavior and avoid a new feature. Either of these options will be |
||||
the next step after the release of 2023, with aliases being preferred as long as |
||||
the timing works out. |
||||
|
||||
If we hit any unexpected delays, Nerf Delimited Encoding in 2023 (not available |
||||
externally) is the quickest path forward to unblock the release of edition 2023. |
||||
It has a lot of downsides though, and will block any migration towards delimited |
||||
encoding until edition 2024 has started rolling out. |
||||
|
||||
## Alternatives |
||||
|
||||
### Smooth Extension {#smooth-extension} |
||||
|
||||
Instead of trying to change the existing behavior, we could expand the current |
||||
spec to try to cover both proto2 and editions. We would define a "group-like" |
||||
concept, which applies to all fields which: |
||||
|
||||
* Have `DELIMITED` encoding |
||||
* Have a type corresponding to a nested message directly under its containing |
||||
message |
||||
* Have a name corresponding to its lowercased type name. |
||||
|
||||
Note that proto2 groups will *always* be "group-like." |
||||
|
||||
For any group-like field we will use the old proto2 semantics, whatever they are |
||||
today. Otherwise, we will treat them as regular fields for both codegen and |
||||
text-format. This means that *most* new cases of delimited encoding will have |
||||
the desired behavior, while *all* old groups will continue to function. The main |
||||
exception here is that users will see the unexpected proto2 behavior if they |
||||
have message/field names that *happen* to match. |
||||
|
||||
While the old behavior will result in some unexpected capitalization when it's |
||||
hit, it's mostly safe. Because of 2 and 3 (and the fact that we disallow |
||||
duplicate field names), we can guarantee that in both codegen and text encoding |
||||
there will never be any conflicting symbols. There can never be two delimited |
||||
fields of the same type using the old behavior, and no other messages or fields |
||||
will exist with either spelling. |
||||
|
||||
Additionally, we will update the text parsers to accept **both** the old |
||||
message-based spelling and the new field-based spelling for group-like fields. |
||||
This will at least prevent parsing failures if users hit this unexpected change |
||||
in behavior. |
||||
|
||||
#### Pros |
||||
|
||||
* Fully supports old proto2 behavior |
||||
* Treats most new editions fields correctly |
||||
* Doesn't allow for any of the problematic cases we see today |
||||
* By updating the parsers to accept both, we have a migration path to change |
||||
the "wire"-format |
||||
* Decoupled from editions launch (since it's a non-breaking change w/o a |
||||
feature) |
||||
|
||||
#### Cons |
||||
|
||||
* Requires coordinated changes in every editions-compatible runtime (and many |
||||
generators) |
||||
* Keeps the old proto2 behavior around indefinitely, with no path to remove it |
||||
* Plants surprising edge case for users if they happen to name their |
||||
message/fields a certain way |
||||
|
||||
### Global Feature {#global-feature} |
||||
|
||||
The simplest answer here is to introduce a new global message feature |
||||
`legacy_group_handling` to control all the changes we'd like. This will only be |
||||
applicable to group-like fields (see |
||||
[Smooth Extension](?tab=t.0#heading=h.blnhard1tpyx)). With this feature enabled, |
||||
these fields will always use their message name for text-format. Each |
||||
non-conformant language could also use this feature to gate the codegen rules. |
||||
|
||||
#### Pros |
||||
|
||||
* Simple boolean to gate all the behavior changes |
||||
* Doesn't require adding language features to a bunch of languages that don't |
||||
have them yet |
||||
* Uses editions to ratchet down the bad behavior |
||||
|
||||
#### Cons |
||||
|
||||
* It's a little late in the game to be introducing new features to 2023 |
||||
(go/edition-lifetimes) |
||||
* Requires coordinated changes in every editions-compatible runtime (and many |
||||
generators) |
||||
* The migration story for users is unclear. Overriding the value of this |
||||
feature is both a "wire"-breaking and API-breaking change they may not be |
||||
able to do easily. |
||||
* With the feature set, users will still see all of the problems we have today |
||||
|
||||
### Feature Suite |
||||
|
||||
An extension of [Global feature](?tab=t.0#heading=h.mvtf74vplkdg) would be to |
||||
split the codegen changes out into separate per-language features. |
||||
|
||||
#### Pros |
||||
|
||||
* Simple booleans to gate all the distinct behavior changes |
||||
* Uses editions to ratchet down the bad behavior |
||||
* Better migration story for users, since it separates API and "wire" breaking |
||||
changes |
||||
|
||||
#### Cons |
||||
|
||||
* Requires a whole slew of new language features, which typically have a |
||||
difficult first-time setup |
||||
* Requires coordinated changes in every editions-compatible runtime (and many |
||||
generators) |
||||
* Increases the complexity of edition 2023 significantly |
||||
* With the features set, users will still see all of the problems we have |
||||
today |
||||
|
||||
### Nerf Delimited Encoding in 2023 |
||||
|
||||
A quick fix to avoid releasing a bad feature would be to simply ban the case |
||||
where the message and field names don't match. Adding this validation to protoc |
||||
would cover the majority of cases, although we might want additional checks in |
||||
every language that supports dynamic messages. |
||||
|
||||
This is a good fallback option if we can't implement anything better before 27.0 |
||||
is released. It allows us to release editions in a reasonable state, where we |
||||
can fix these issues and release a more functional `DELIMITED` feature in 2024. |
||||
|
||||
#### Pros |
||||
|
||||
* Unblocks editions rollout |
||||
* Easy and safe to implement |
||||
* Avoids rushed implementation of a proper fix |
||||
* Avoids runtime issues with text format |
||||
* Avoids unexpected build breakages post-editions (e.g. renaming the nested |
||||
message) |
||||
|
||||
#### Cons |
||||
|
||||
* We'd still be releasing a really bad feature. Instead of opening up new |
||||
possibilities, it's just "like groups but worse" |
||||
* We couldn't fix this in 2023 without potential version skew from third party |
||||
plugins. We'd likely have to wait until edition 2024 |
||||
* Might requires coordinated changes in a lot of runtimes |
||||
* Doesn't unblock our effort to roll out delimited |
||||
|
||||
### Rename Fields in Editions |
||||
|
||||
While it might be tempting to leverage the edition 2023 upgrade as a place we |
||||
can just rename the group field, that doesn't actually work (e.g. rename |
||||
`mygroup` to `my_group`). Because so many runtimes already use the *field name* |
||||
in generated APIs, they would break under this transformation. |
||||
|
||||
#### Pros |
||||
|
||||
* Works really well for text-format and some languages |
||||
|
||||
#### Cons |
||||
|
||||
* Turns 2023 upgrade into a breaking change for many languages |
||||
|
||||
### Aliases {#aliases} |
||||
|
||||
We've discussed aliases a lot mostly in the context of `Any`, but they would be |
||||
useful for any encoding scheme that locks down field/message names. If we had a |
||||
fully implemented alias system in place, it would be the perfect mitigation |
||||
here. Unfortunately, we don't yet and the timeline here is probably too tight to |
||||
implement one. |
||||
|
||||
#### Pros |
||||
|
||||
* Fixes all of the problems mentioned above |
||||
* Allows us to specify the old behavior using the proto language, which allows |
||||
it to be handled by Prototiller |
||||
|
||||
#### Cons |
||||
|
||||
* We want this to be a real fully thought-out feature, not a hack rushed into |
||||
a tight timeline |
||||
|
||||
### Do Nothing |
||||
|
||||
Doing nothing doesn't actually break anyone, but it is embarrassing. |
||||
|
||||
#### Pros |
||||
|
||||
* Easy to do |
||||
|
||||
#### Cons |
||||
|
||||
* Releases a horrible feature full of foot-guns in our first edition |
||||
* Doesn't unblock our effort to roll out delimited |
@ -1 +1,9 @@ |
||||
build --cxxopt=-std=c++14 --host_cxxopt=-std=c++14 |
||||
common --enable_platform_specific_config |
||||
|
||||
build:linux --cxxopt=-std=c++14 --host_cxxopt=-std=c++14 |
||||
build:macos --cxxopt=-std=c++14 --host_cxxopt=-std=c++14 |
||||
|
||||
common:windows --enable_runfiles |
||||
|
||||
build --experimental_remote_cache_eviction_retries=5 |
||||
build --remote_download_outputs=all |
||||
|
@ -0,0 +1,79 @@ |
||||
package com.google.protobuf; |
||||
|
||||
import com.google.protobuf.Descriptors.FieldDescriptor; |
||||
|
||||
/** |
||||
* Provides an explicit API for unstable, redacting debug output suitable for debug logging. This |
||||
* implementation is based on TextFormat, but should not be parsed. |
||||
*/ |
||||
public final class DebugFormat { |
||||
|
||||
private final boolean isSingleLine; |
||||
|
||||
private DebugFormat(boolean singleLine) { |
||||
isSingleLine = singleLine; |
||||
} |
||||
|
||||
public static DebugFormat singleLine() { |
||||
return new DebugFormat(true); |
||||
} |
||||
|
||||
public static DebugFormat multiline() { |
||||
return new DebugFormat(false); |
||||
} |
||||
|
||||
public String toString(MessageOrBuilder message) { |
||||
return TextFormat.printer() |
||||
.emittingSingleLine(this.isSingleLine) |
||||
.enablingSafeDebugFormat(true) |
||||
.printToString(message); |
||||
} |
||||
|
||||
public String toString(FieldDescriptor field, Object value) { |
||||
return TextFormat.printer() |
||||
.emittingSingleLine(this.isSingleLine) |
||||
.enablingSafeDebugFormat(true) |
||||
.printFieldToString(field, value); |
||||
} |
||||
|
||||
public String toString(UnknownFieldSet fields) { |
||||
return TextFormat.printer() |
||||
.emittingSingleLine(this.isSingleLine) |
||||
.enablingSafeDebugFormat(true) |
||||
.printToString(fields); |
||||
} |
||||
|
||||
public Object lazyToString(MessageOrBuilder message) { |
||||
return new LazyDebugOutput(message, this); |
||||
} |
||||
|
||||
public Object lazyToString(UnknownFieldSet fields) { |
||||
return new LazyDebugOutput(fields, this); |
||||
} |
||||
|
||||
private static class LazyDebugOutput { |
||||
private final MessageOrBuilder message; |
||||
private final UnknownFieldSet fields; |
||||
private final DebugFormat format; |
||||
|
||||
LazyDebugOutput(MessageOrBuilder message, DebugFormat format) { |
||||
this.message = message; |
||||
this.fields = null; |
||||
this.format = format; |
||||
} |
||||
|
||||
LazyDebugOutput(UnknownFieldSet fields, DebugFormat format) { |
||||
this.message = null; |
||||
this.fields = fields; |
||||
this.format = format; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
if (message != null) { |
||||
return format.toString(message); |
||||
} |
||||
return format.toString(fields); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
package com.google.protobuf; |
||||
|
||||
import com.google.protobuf.Internal.ProtobufList; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Utility class that aids in properly manipulating list fields for either the lite or full runtime. |
||||
*/ |
||||
@CheckReturnValue |
||||
final class ListFieldSchemaFull implements ListFieldSchema { |
||||
|
||||
private static final Class<?> UNMODIFIABLE_LIST_CLASS = |
||||
Collections.unmodifiableList(Collections.emptyList()).getClass(); |
||||
|
||||
@Override |
||||
public <L> List<L> mutableListAt(Object message, long offset) { |
||||
return mutableListAt(message, offset, AbstractProtobufList.DEFAULT_CAPACITY); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private static <L> List<L> mutableListAt(Object message, long offset, int additionalCapacity) { |
||||
List<L> list = getList(message, offset); |
||||
if (list.isEmpty()) { |
||||
if (list instanceof LazyStringList) { |
||||
list = (List<L>) new LazyStringArrayList(additionalCapacity); |
||||
} else if (list instanceof PrimitiveNonBoxingCollection && list instanceof ProtobufList) { |
||||
list = ((ProtobufList<L>) list).mutableCopyWithCapacity(additionalCapacity); |
||||
} else { |
||||
list = new ArrayList<L>(additionalCapacity); |
||||
} |
||||
UnsafeUtil.putObject(message, offset, list); |
||||
} else if (UNMODIFIABLE_LIST_CLASS.isAssignableFrom(list.getClass())) { |
||||
ArrayList<L> newList = new ArrayList<L>(list.size() + additionalCapacity); |
||||
newList.addAll(list); |
||||
list = newList; |
||||
UnsafeUtil.putObject(message, offset, list); |
||||
} else if (list instanceof UnmodifiableLazyStringList) { |
||||
LazyStringArrayList newList = new LazyStringArrayList(list.size() + additionalCapacity); |
||||
newList.addAll((UnmodifiableLazyStringList) list); |
||||
list = (List<L>) newList; |
||||
UnsafeUtil.putObject(message, offset, list); |
||||
} else if (list instanceof PrimitiveNonBoxingCollection |
||||
&& list instanceof ProtobufList |
||||
&& !((ProtobufList<L>) list).isModifiable()) { |
||||
list = ((ProtobufList<L>) list).mutableCopyWithCapacity(list.size() + additionalCapacity); |
||||
UnsafeUtil.putObject(message, offset, list); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
@Override |
||||
public void makeImmutableListAt(Object message, long offset) { |
||||
List<?> list = (List<?>) UnsafeUtil.getObject(message, offset); |
||||
Object immutable = null; |
||||
if (list instanceof LazyStringList) { |
||||
immutable = ((LazyStringList) list).getUnmodifiableView(); |
||||
} else if (UNMODIFIABLE_LIST_CLASS.isAssignableFrom(list.getClass())) { |
||||
// already immutable
|
||||
return; |
||||
} else if (list instanceof PrimitiveNonBoxingCollection && list instanceof ProtobufList) { |
||||
if (((ProtobufList<?>) list).isModifiable()) { |
||||
((ProtobufList<?>) list).makeImmutable(); |
||||
} |
||||
return; |
||||
} else { |
||||
immutable = Collections.unmodifiableList((List<?>) list); |
||||
} |
||||
UnsafeUtil.putObject(message, offset, immutable); |
||||
} |
||||
|
||||
@Override |
||||
public <E> void mergeListsAt(Object msg, Object otherMsg, long offset) { |
||||
List<E> other = getList(otherMsg, offset); |
||||
List<E> mine = mutableListAt(msg, offset, other.size()); |
||||
|
||||
int size = mine.size(); |
||||
int otherSize = other.size(); |
||||
if (size > 0 && otherSize > 0) { |
||||
mine.addAll(other); |
||||
} |
||||
|
||||
List<E> merged = size > 0 ? mine : other; |
||||
UnsafeUtil.putObject(msg, offset, merged); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
static <E> List<E> getList(Object message, long offset) { |
||||
return (List<E>) UnsafeUtil.getObject(message, offset); |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
package com.google.protobuf; |
||||
|
||||
import com.google.protobuf.Internal.ProtobufList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Utility class that aids in properly manipulating list fields for either the lite or full runtime. |
||||
*/ |
||||
final class ListFieldSchemaLite implements ListFieldSchema { |
||||
|
||||
@Override |
||||
public <L> List<L> mutableListAt(Object message, long offset) { |
||||
ProtobufList<L> list = getProtobufList(message, offset); |
||||
if (!list.isModifiable()) { |
||||
int size = list.size(); |
||||
list = |
||||
list.mutableCopyWithCapacity( |
||||
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2); |
||||
UnsafeUtil.putObject(message, offset, list); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
@Override |
||||
public void makeImmutableListAt(Object message, long offset) { |
||||
ProtobufList<?> list = getProtobufList(message, offset); |
||||
list.makeImmutable(); |
||||
} |
||||
|
||||
@Override |
||||
public <E> void mergeListsAt(Object msg, Object otherMsg, long offset) { |
||||
ProtobufList<E> mine = getProtobufList(msg, offset); |
||||
ProtobufList<E> other = getProtobufList(otherMsg, offset); |
||||
|
||||
int size = mine.size(); |
||||
int otherSize = other.size(); |
||||
if (size > 0 && otherSize > 0) { |
||||
if (!mine.isModifiable()) { |
||||
mine = mine.mutableCopyWithCapacity(size + otherSize); |
||||
} |
||||
mine.addAll(other); |
||||
} |
||||
|
||||
ProtobufList<E> merged = size > 0 ? mine : other; |
||||
UnsafeUtil.putObject(msg, offset, merged); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
static <E> ProtobufList<E> getProtobufList(Object message, long offset) { |
||||
return (ProtobufList<E>) UnsafeUtil.getObject(message, offset); |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
package com.google.protobuf; |
||||
|
||||
@CheckReturnValue |
||||
final class ListFieldSchemas { |
||||
private static final ListFieldSchema FULL_SCHEMA = loadSchemaForFullRuntime(); |
||||
private static final ListFieldSchema LITE_SCHEMA = new ListFieldSchemaLite(); |
||||
|
||||
static ListFieldSchema full() { |
||||
return FULL_SCHEMA; |
||||
} |
||||
|
||||
static ListFieldSchema lite() { |
||||
return LITE_SCHEMA; |
||||
} |
||||
|
||||
private static ListFieldSchema loadSchemaForFullRuntime() { |
||||
try { |
||||
Class<?> clazz = Class.forName("com.google.protobuf.ListFieldSchemaFull"); |
||||
return (ListFieldSchema) clazz.getDeclaredConstructor().newInstance(); |
||||
} catch (Exception e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private ListFieldSchemas() {} |
||||
} |
@ -0,0 +1,210 @@ |
||||
package com.google.protobuf; |
||||
|
||||
import static com.google.common.truth.Truth.assertThat; |
||||
import static protobuf_unittest.UnittestProto.redactedExtension; |
||||
|
||||
import com.google.protobuf.Descriptors.FieldDescriptor; |
||||
import protobuf_unittest.UnittestProto.RedactedFields; |
||||
import protobuf_unittest.UnittestProto.TestEmptyMessage; |
||||
import protobuf_unittest.UnittestProto.TestNestedMessageRedaction; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.junit.runners.JUnit4; |
||||
|
||||
@RunWith(JUnit4.class) |
||||
public final class DebugFormatTest { |
||||
|
||||
private static final String REDACTED_REGEX = "\\[REDACTED\\]"; |
||||
private static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true); |
||||
private static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false); |
||||
|
||||
private static String getUnstablePrefix(boolean singleLine) { |
||||
return ""; |
||||
} |
||||
|
||||
@Test |
||||
public void multilineMessageFormat_returnsMultiline() { |
||||
RedactedFields message = RedactedFields.newBuilder().setOptionalUnredactedString("foo").build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
assertThat(result) |
||||
.matches( |
||||
String.format("%soptional_unredacted_string: \"foo\"\n", UNSTABLE_PREFIX_MULTILINE)); |
||||
} |
||||
|
||||
@Test |
||||
public void singleLineMessageFormat_returnsSingleLine() { |
||||
RedactedFields message = RedactedFields.newBuilder().setOptionalUnredactedString("foo").build(); |
||||
|
||||
String result = DebugFormat.singleLine().toString(message); |
||||
assertThat(result) |
||||
.matches( |
||||
String.format("%soptional_unredacted_string: \"foo\"", UNSTABLE_PREFIX_SINGLE_LINE)); |
||||
} |
||||
|
||||
@Test |
||||
public void messageFormat_debugRedactFieldIsRedacted() { |
||||
RedactedFields message = RedactedFields.newBuilder().setOptionalRedactedString("foo").build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
|
||||
assertThat(result) |
||||
.matches( |
||||
String.format( |
||||
"%soptional_redacted_string: %s\n", UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX)); |
||||
} |
||||
|
||||
@Test |
||||
public void messageFormat_debugRedactMessageIsRedacted() { |
||||
RedactedFields message = |
||||
RedactedFields.newBuilder() |
||||
.setOptionalRedactedMessage( |
||||
TestNestedMessageRedaction.newBuilder().setOptionalUnredactedNestedString("foo")) |
||||
.build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
|
||||
assertThat(result) |
||||
.matches( |
||||
String.format( |
||||
"%soptional_redacted_message \\{\n %s\n\\}\n", |
||||
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX)); |
||||
} |
||||
|
||||
@Test |
||||
public void messageFormat_debugRedactMapIsRedacted() { |
||||
RedactedFields message = RedactedFields.newBuilder().putMapRedactedString("foo", "bar").build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
|
||||
assertThat(result) |
||||
.matches( |
||||
String.format( |
||||
"%smap_redacted_string \\{\\n %s\n\\}\n", |
||||
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX)); |
||||
} |
||||
|
||||
@Test |
||||
public void messageFormat_debugRedactExtensionIsRedacted() { |
||||
RedactedFields message = |
||||
RedactedFields.newBuilder().setExtension(redactedExtension, "foo").build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
|
||||
assertThat(result) |
||||
.matches( |
||||
String.format( |
||||
"%s\\[protobuf_unittest\\.redacted_extension\\]: %s\n", |
||||
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX)); |
||||
} |
||||
|
||||
@Test |
||||
public void messageFormat_redactFalseIsNotRedacted() { |
||||
RedactedFields message = |
||||
RedactedFields.newBuilder().setOptionalRedactedFalseString("foo").build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
assertThat(result) |
||||
.matches( |
||||
String.format( |
||||
"%soptional_redacted_false_string: \"foo\"\n", UNSTABLE_PREFIX_MULTILINE)); |
||||
} |
||||
|
||||
@Test |
||||
public void messageFormat_nonSensitiveFieldIsNotRedacted() { |
||||
RedactedFields message = RedactedFields.newBuilder().setOptionalUnredactedString("foo").build(); |
||||
|
||||
String result = DebugFormat.multiline().toString(message); |
||||
|
||||
assertThat(result) |
||||
.matches( |
||||
String.format("%soptional_unredacted_string: \"foo\"\n", UNSTABLE_PREFIX_MULTILINE)); |
||||
} |
||||
|
||||
@Test |
||||
public void descriptorDebugFormat_returnsExpectedFormat() { |
||||
FieldDescriptor field = |
||||
RedactedFields.getDescriptor().findFieldByName("optional_redacted_string"); |
||||
String result = DebugFormat.multiline().toString(field, "foo"); |
||||
assertThat(result) |
||||
.matches( |
||||
String.format( |
||||
"%soptional_redacted_string: %s\n", UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX)); |
||||
} |
||||
|
||||
@Test |
||||
public void unstableFormat_isStablePerProcess() { |
||||
RedactedFields message1 = |
||||
RedactedFields.newBuilder().setOptionalUnredactedString("foo").build(); |
||||
RedactedFields message2 = |
||||
RedactedFields.newBuilder().setOptionalUnredactedString("foo").build(); |
||||
for (int i = 0; i < 5; i++) { |
||||
String result1 = DebugFormat.multiline().toString(message1); |
||||
String result2 = DebugFormat.multiline().toString(message2); |
||||
assertThat(result1).isEqualTo(result2); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void lazyDebugFormatMessage_supportsImplicitFormatting() { |
||||
RedactedFields message = RedactedFields.newBuilder().setOptionalUnredactedString("foo").build(); |
||||
|
||||
Object lazyDebug = DebugFormat.singleLine().lazyToString(message); |
||||
|
||||
assertThat(String.format("%s", lazyDebug)) |
||||
.matches( |
||||
String.format("%soptional_unredacted_string: \"foo\"", UNSTABLE_PREFIX_SINGLE_LINE)); |
||||
} |
||||
|
||||
private UnknownFieldSet makeUnknownFieldSet() { |
||||
return UnknownFieldSet.newBuilder() |
||||
.addField( |
||||
5, |
||||
UnknownFieldSet.Field.newBuilder() |
||||
.addVarint(1) |
||||
.addFixed32(2) |
||||
.addFixed64(3) |
||||
.addLengthDelimited(ByteString.copyFromUtf8("4")) |
||||
.addLengthDelimited( |
||||
UnknownFieldSet.newBuilder() |
||||
.addField(12, UnknownFieldSet.Field.newBuilder().addVarint(6).build()) |
||||
.build() |
||||
.toByteString()) |
||||
.addGroup( |
||||
UnknownFieldSet.newBuilder() |
||||
.addField(10, UnknownFieldSet.Field.newBuilder().addVarint(5).build()) |
||||
.build()) |
||||
.build()) |
||||
.addField( |
||||
8, UnknownFieldSet.Field.newBuilder().addVarint(1).addVarint(2).addVarint(3).build()) |
||||
.addField( |
||||
15, |
||||
UnknownFieldSet.Field.newBuilder() |
||||
.addVarint(0xABCDEF1234567890L) |
||||
.addFixed32(0xABCD1234) |
||||
.addFixed64(0xABCDEF1234567890L) |
||||
.build()) |
||||
.build(); |
||||
} |
||||
|
||||
@Test |
||||
public void unknownFieldsDebugFormat_returnsExpectedFormat() { |
||||
TestEmptyMessage unknownFields = |
||||
TestEmptyMessage.newBuilder().setUnknownFields(makeUnknownFieldSet()).build(); |
||||
|
||||
assertThat(DebugFormat.multiline().toString(unknownFields)) |
||||
.matches( |
||||
String.format("%s5: UNKNOWN_VARINT %s\n", UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX) |
||||
+ String.format("5: UNKNOWN_FIXED32 %s\n", REDACTED_REGEX) |
||||
+ String.format("5: UNKNOWN_FIXED64 %s\n", REDACTED_REGEX) |
||||
+ String.format("5: UNKNOWN_STRING %s\n", REDACTED_REGEX) |
||||
+ String.format("5: \\{\n 12: UNKNOWN_VARINT %s\n\\}\n", REDACTED_REGEX) |
||||
+ String.format("5 \\{\n 10: UNKNOWN_VARINT %s\n\\}\n", REDACTED_REGEX) |
||||
+ String.format("8: UNKNOWN_VARINT %s\n", REDACTED_REGEX) |
||||
+ String.format("8: UNKNOWN_VARINT %s\n", REDACTED_REGEX) |
||||
+ String.format("8: UNKNOWN_VARINT %s\n", REDACTED_REGEX) |
||||
+ String.format("15: UNKNOWN_VARINT %s\n", REDACTED_REGEX) |
||||
+ String.format("15: UNKNOWN_FIXED32 %s\n", REDACTED_REGEX) |
||||
+ String.format("15: UNKNOWN_FIXED64 %s\n", REDACTED_REGEX)); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue