Skip to content

Commit

Permalink
Protobuf Java Cross-version validation in OSS.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 585139332
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Nov 24, 2023
1 parent 62d5d9b commit a2f9268
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 16 deletions.
1 change: 1 addition & 0 deletions java/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/Proto2SchemaTest.java",
"src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java",
"src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java",
"src/test/java/com/google/protobuf/RuntimeVersionTest.java",
"src/test/java/com/google/protobuf/ServiceTest.java",
"src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.java",
"src/test/java/com/google/protobuf/TestBadIdentifiers.java",
Expand Down
74 changes: 58 additions & 16 deletions java/core/src/main/java/com/google/protobuf/RuntimeVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

/**
* Provides the version of this Protobuf Java runtime, and methods for Protobuf Java gencode to
* validate that versions are compatible.
* validate that versions are compatible. Fields and methods in this class should be only accessed
* by related unit tests and Protobuf Java gencode, and should not be used elsewhere.
*/
public final class RuntimeVersion {

Expand All @@ -19,35 +20,71 @@ public enum RuntimeDomain {
PUBLIC,
}

// The version information for this runtime.
// The version of this runtime.
// Automatically updated by Protobuf release process. Do not edit manually.
private static final RuntimeDomain DOMAIN = RuntimeDomain.PUBLIC;
private static final int MAJOR = 3;
private static final int MINOR = 26;
private static final int PATCH = 0;
private static final String SUFFIX = "-dev";
public static final RuntimeDomain DOMAIN = RuntimeDomain.PUBLIC;
public static final int MAJOR = 3;
public static final int MINOR = 26;
public static final int PATCH = 0;
public static final String SUFFIX = "-dev";
private static final String VERSION_STRING = versionString(MAJOR, MINOR, PATCH, SUFFIX);

/**
* Validates that the gencode version is compatible with this runtime version. Currently, no
* validation takes place, but only checks that version numbers valid.
* Validates that the gencode version is compatible with this runtime version according to
* https://protobuf.dev/support/cross-version-runtime-guarantee/.
*
* <p>This method is only for Protobuf Java gencode; do not call it elsewhere.
* <p>This method is currently only used by Protobuf Java gencode in OSS.
*
* <p>In the future, we will validate Protobuf Java versions according to
* https://protobuf.dev/support/cross-version-runtime-guarantee/
* <p>This method is only for Protobuf Java gencode; do not call it elsewhere.
*
* @param domain the domain where Protobuf Java code was generated. Currently unused.
* @param domain the domain where Protobuf Java code was generated. Currently ignored.
* @param major the major version of Protobuf Java gencode.
* @param minor the minor version of Protobuf Java gencode.
* @param patch the micro/patch version of Protobuf Java gencode.
* @param suffix the version suffix e.g. "-rc2", "-dev", etc. Currently unused.
* @param suffix the version suffix e.g. "-rc2", "-dev", etc.
* @throws ProtobufRuntimeVersionException if versions are incompatible.
*/
public static void validateProtobufGencodeVersion(
RuntimeDomain domain, int major, int minor, int patch, String suffix) {
// TODO: b/298200443 - Add cross-version validations.

// Check the environmental variable, and temporarily disable poison pills if it's set to true.
String disableFlag = java.lang.System.getenv("TEMORARILY_DISABLE_PROTOBUF_VERSION_CHECK");
if (disableFlag != null && disableFlag.equals("true")) {
return;
}

// Check that version numbers are valid.
if (major < 0 || minor < 0 || patch < 0) {
throw new ProtobufRuntimeVersionException(
String.format("Invalid gencode version: %d.%d.%d", major, minor, patch));
"Invalid gencode version: " + versionString(major, minor, patch, suffix));
}

String gencodeVersionString = versionString(major, minor, patch, suffix);
// Check that runtime major version is the same as the gencode major version.
if (major != MAJOR) {
throw new ProtobufRuntimeVersionException(
String.format(
"Mismatched Protobuf Gencode/Runtime major versions: gencode %s, runtime %s. Same"
+ " major version is required.",
gencodeVersionString, VERSION_STRING));
}

// Check that runtime version is newer than the gencode version.
if (MINOR < minor || (MINOR == minor && PATCH < patch)) {
throw new ProtobufRuntimeVersionException(
String.format(
"Protobuf Java runtime version cannot be older than the gencode version:"
+ "gencode %s, runtime %s.",
gencodeVersionString, VERSION_STRING));
}

// Check that runtime version suffix is the same as the gencode version suffix.
if (!suffix.equals(SUFFIX)) {
throw new ProtobufRuntimeVersionException(
String.format(
"Mismatched Protobuf Gencode/Runtime version suffixes: gencode %s, runtime %s."
+ " Version suffixes must be the same.",
gencodeVersionString, VERSION_STRING));
}
}

Expand All @@ -60,4 +97,9 @@ public ProtobufRuntimeVersionException(String message) {
super(message);
}
}

/** Gets the version string given the version segments. */
private static String versionString(int major, int minor, int patch, String suffix) {
return String.format("%d.%d.%d%s", major, minor, patch, suffix);
}
}
124 changes: 124 additions & 0 deletions java/core/src/test/java/com/google/protobuf/RuntimeVersionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class RuntimeVersionTest {

@Test
public void versionValidation_invalidVersionNumbers() {
RuntimeVersion.ProtobufRuntimeVersionException thrown =
assertThrows(
RuntimeVersion.ProtobufRuntimeVersionException.class,
() ->
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
-1,
RuntimeVersion.MINOR,
RuntimeVersion.PATCH,
RuntimeVersion.SUFFIX));
assertThat(thrown).hasMessageThat().contains("Invalid gencode version: -1");
}

@Test
public void versionValidation_mismatchingMajorDisallowed() {
int gencodeMajor = 1;
RuntimeVersion.ProtobufRuntimeVersionException thrown =
assertThrows(
RuntimeVersion.ProtobufRuntimeVersionException.class,
() ->
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
gencodeMajor,
RuntimeVersion.MINOR,
RuntimeVersion.PATCH,
RuntimeVersion.SUFFIX));
assertThat(thrown)
.hasMessageThat()
.contains("Mismatched Protobuf Gencode/Runtime major versions");
}

@Test
public void versionValidation_versionNumbersAllTheSameAllowed() {
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
RuntimeVersion.MAJOR,
RuntimeVersion.MINOR,
RuntimeVersion.PATCH,
RuntimeVersion.SUFFIX);
}

@Test
public void versionValidation_NewerRuntimeVersionAllowed() {
int gencodeMinor = RuntimeVersion.MINOR - 1;
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
RuntimeVersion.MAJOR,
gencodeMinor,
RuntimeVersion.PATCH,
RuntimeVersion.SUFFIX);
}

@Test
public void versionValidation_OlderRuntimeVersionDisallowed() {
int gencodeMinor = RuntimeVersion.MINOR + 1;
RuntimeVersion.ProtobufRuntimeVersionException thrown =
assertThrows(
RuntimeVersion.ProtobufRuntimeVersionException.class,
() ->
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
RuntimeVersion.MAJOR,
gencodeMinor,
RuntimeVersion.PATCH,
RuntimeVersion.SUFFIX));
assertThat(thrown)
.hasMessageThat()
.contains("Protobuf Java runtime version cannot be older than the gencode version");

int gencodePatch = RuntimeVersion.PATCH + 1;
thrown =
assertThrows(
RuntimeVersion.ProtobufRuntimeVersionException.class,
() ->
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
RuntimeVersion.MAJOR,
RuntimeVersion.MINOR,
gencodePatch,
RuntimeVersion.SUFFIX));
assertThat(thrown)
.hasMessageThat()
.contains("Protobuf Java runtime version cannot be older than the gencode version");
}

@Test
public void versionValidation_differentVesionSuffixDisallowed() {
String gencodeSuffix = "-test";
RuntimeVersion.ProtobufRuntimeVersionException thrown =
assertThrows(
RuntimeVersion.ProtobufRuntimeVersionException.class,
() ->
RuntimeVersion.validateProtobufGencodeVersion(
RuntimeVersion.DOMAIN,
RuntimeVersion.MAJOR,
RuntimeVersion.MINOR,
RuntimeVersion.PATCH,
gencodeSuffix));
assertThat(thrown)
.hasMessageThat()
.contains("Mismatched Protobuf Gencode/Runtime version suffixes");
}
}
1 change: 1 addition & 0 deletions java/lite/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
<exclude>Proto2SchemaTest.java</exclude>
<exclude>Proto2UnknownEnumValueTest.java</exclude>
<exclude>RepeatedFieldBuilderV3Test.java</exclude>
<exclude>RuntimeVersionTest.java</exclude>
<exclude>ServiceTest.java</exclude>
<exclude>SingleFieldBuilderV3Test.java</exclude>
<exclude>TestBadIdentifiers.java</exclude>
Expand Down

0 comments on commit a2f9268

Please sign in to comment.