Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ on:
- master

jobs:
test:
name: "JDK ${{ matrix.java }}"
strategy:
matrix:
java: [ 8, 11 ]
build:
name: 'Build with JDK 11'
runs-on: ubuntu-latest
steps:
# Cancel any previous runs for the same branch that are still running.
Expand All @@ -23,25 +20,54 @@ jobs:
access_token: ${{ github.token }}
- name: 'Check out repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: 'Set up JDK ${{ matrix.java }}'
- name: 'Set up JDK 11 for compilation'
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
with:
java-version: ${{ matrix.java }}
java-version: 11
distribution: 'zulu'
cache: 'maven'
- name: 'Install'
shell: bash
run: mvn -B -P!standard-with-extra-repos install -U -DskipTests=true
- name: 'Test'
shell: bash
run: mvn -B -P!standard-with-extra-repos verify -U -Dmaven.javadoc.skip=true
- name: 'Javadoc Test Run'
shell: bash
run: mvn -B -P!standard-with-extra-repos javadoc:aggregate -U
- name: 'Upload build artifacts'
uses: actions/upload-artifact@v3
with:
name: truth-jars
path: |
**/target/*.jar
!**/target/*-sources.jar
!**/target/*-javadoc.jar

test:
name: "Test with JDK ${{ matrix.java }}"
needs: build
strategy:
matrix:
java: [ 8, 11, 17 ]
runs-on: ubuntu-latest
steps:
- name: 'Check out repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: 'Set up JDK ${{ matrix.java }} for testing'
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165
with:
java-version: ${{ matrix.java }}
distribution: 'zulu'
cache: 'maven'
- name: 'Download build artifacts'
uses: actions/download-artifact@v3
with:
name: truth-jars
- name: 'Test'
shell: bash
run: mvn -B -P!standard-with-extra-repos verify -U -Dmaven.javadoc.skip=true

publish_snapshot:
name: 'Publish snapshot'
needs: test
needs: [build, test]
if: github.event_name == 'push' && github.repository == 'google/truth'
runs-on: ubuntu-latest
steps:
Expand All @@ -66,7 +92,7 @@ jobs:
permissions:
contents: write
name: 'Generate latest docs'
needs: test
needs: [build, test]
if: github.event_name == 'push' && github.repository == 'google/truth'
runs-on: ubuntu-latest
steps:
Expand Down
9 changes: 8 additions & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.google.truth</groupId>
<artifactId>truth-parent</artifactId>
<version>HEAD-SNAPSHOT</version>
<version>999.0.0-SNAPSHOT</version>
</parent>
<artifactId>truth</artifactId>
<name>Truth Core</name>
Expand Down Expand Up @@ -110,6 +110,13 @@
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current configuration, I don't think we actually end up producing a multi-release jar, so I think we can get by without this.

</manifestEntries>
</archive>
</configuration>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error #2 from mvn clean install:

[INFO] Running com.google.common.truth.CorrespondenceExceptionStoreTest
[ERROR] Tests run: 6, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.009 s <<< FAILURE! -- in com.google.common.truth.CorrespondenceExceptionStoreTest
[ERROR] com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsMainCause_notEmpty -- Time elapsed: 0.007 s <<< FAILURE!
value of:
    getValue()
expected to match:
    compare\(null, 123\) threw com.google.common.truth.TestCorrespondences\$NullPointerExceptionFromWithin10Of\s+at com\.google\.common\.truth\.TestCorrespondences(.|\n)*\n---
but was:
    compare(null, 123) threw com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of
        at [email protected]/com.google.common.truth.TestCorrespondences.lambda$static$1(TestCorrespondences.java:76)
        at [email protected]/com.google.common.truth.Correspondence$FromBinaryPredicate.compare(Correspondence.java:150)
        at [email protected]/com.google.common.truth.Correspondence$FormattingDiffs.compare(Correspondence.java:453)
    
    ---
        at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.assertExpectedFacts(CorrespondenceExceptionStoreTest.java:99)
        at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsMainCause_notEmpty(CorrespondenceExceptionStoreTest.java:58)
 
[ERROR] com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsAdditionalInfo_notEmpty -- Time elapsed: 0.002 s <<< FAILURE!
value of:
    getValue()
expected to match:
    compare\(null, 123\) threw com.google.common.truth.TestCorrespondences\$NullPointerExceptionFromWithin10Of\s+at com\.google\.common\.truth\.TestCorrespondences(.|\n)*\n---
but was:
    compare(null, 123) threw com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of
        at [email protected]/com.google.common.truth.TestCorrespondences.lambda$static$1(TestCorrespondences.java:76)
        at [email protected]/com.google.common.truth.Correspondence$FromBinaryPredicate.compare(Correspondence.java:150)
        at [email protected]/com.google.common.truth.Correspondence$FormattingDiffs.compare(Correspondence.java:453)
    
    ---
        at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.assertExpectedFacts(CorrespondenceExceptionStoreTest.java:99)
        at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsAdditionalInfo_notEmpty(CorrespondenceExceptionStoreTest.java:73)
 

That should just be a matter of loosening the assertion to accept either style.

Of course, another option for this (and for Error #1) is to see if we can keep maven-surefire-plugin out of "modules mode" for its testing. Having the modular mode set up is nice if it's straightforward, but I don't want you to feel like you need to block the user-facing improvements you need on an internal build improvement, albeit one that might someday help us catch modules-related bugs before you encounter them.

<executions>
<execution>
<id>attach-gwt-sources</id>
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

module com.google.truth {
requires com.google.common;
requires static junit;
requires java.compiler;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this is for @Generated from AutoValue:

[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/core/target/generated-sources/annotations/com/google/common/truth/AutoValue_ActualValueInference_OpaqueEntry.java:[4,24] package javax.annotation.processing is not visible
[ERROR]   (package javax.annotation.processing is declared in module java.compiler, but module com.google.truth does not read it)
$ sed -n '4 p' core/target/generated-sources/annotations/com/google/common/truth/AutoValue_ActualValueInference_OpaqueEntry.java
import javax.annotation.processing.Generated;

If I were more ambitious, I would think about this more and possibly open an issue against AutoValue or AutoCommon or something.

It might be that we could avoid this by not performing a "real" Java 9 build, instead only using Java 9 for the module-info build, as I think you had things initially. A downside to that is that we could more easily omit lines from the module-info that we ought to have included (as ably demonstrated by Guava in google/guava#7744 and google/guava#7748). (And any change gives us a chance to get anything wrong.)

I think I'm tentatively in favor of leaving this line here but tweaking it to requires static (here and in whatever other artifacts you are up for pushing through):

Suggested change
requires java.compiler;
requires static java.compiler;


requires static org.jspecify;
requires static com.google.errorprone.annotations;
requires static org.objectweb.asm;
requires static auto.value.annotations;
requires static com.google.j2objc.annotations;

exports com.google.common.truth;
}
2 changes: 1 addition & 1 deletion extensions/java8/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>com.google.truth.extensions</groupId>
<artifactId>truth-extensions-parent</artifactId>
<version>HEAD-SNAPSHOT</version>
<version>999.0.0-SNAPSHOT</version>
</parent>
<artifactId>truth-java8-extension</artifactId>
<name>Obsolete Truth Extension for Java8</name>
Expand Down
12 changes: 11 additions & 1 deletion extensions/liteproto/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>com.google.truth.extensions</groupId>
<artifactId>truth-extensions-parent</artifactId>
<version>HEAD-SNAPSHOT</version>
<version>999.0.0-SNAPSHOT</version>
</parent>
<artifactId>truth-liteproto-extension</artifactId>
<name>Truth Extension for Lite Protocol Buffers</name>
Expand Down Expand Up @@ -97,6 +97,16 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error from mvn clean install -DskipTests:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.14.0:testCompile (default-testCompile) on project truth-liteproto-extension: Compilation failure: Compilation failure:
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[24,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[25,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[26,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[27,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[28,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist

I haven't thought about that one, but it might be a matter of moving more stuff or updating more references. I'd encourage you not to worry about it until we discuss the proto/liteproto business more, but I'm noting it here as a record of potential remaining work.

</plugins>
</build>
</project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@
* limitations under the License.
*/

package com.google.common.truth.extensions.proto;
package com.google.common.truth.extensions.liteproto;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have thought of this, too. It serves me right for saying how "unlikely" it was that we'd need to release a Truth 2.0.0... :)

Changing the package is a tough sell. We might still be able to pull it off, but it would take some effort for both us and our users, including an internal migration that would probably be mostly straightforward but would take some time.

I'm wondering about a potential alternative: What if we were to put both ProtoTruth and LiteProtoTruth into a single artifact with a dependency on protobuf-java that is optional?

Ideally we'd call that artifact truth-proto-extension, but we might "need to" call it truth-liteproto-extension instead: That would ensure that users of both of the existing artifacts get the combined artifact automatically—directly for users of truth-liteproto-extension, transitively for users of truth-proto-extension (which we'd continue to release but which would become an empty jar that still depends on truth-liteproto-extension). (If a build tool forbids use of transitive dependencies, then users of that tool will have to explicitly move from truth-proto-extension to truth-liteproto-extension.)

This would be roughly the approach that we already used when merging the Java 8 extensions into the core of Truth. The difference would be that, in our previous merge, we could reasonably expect for users of the Java 8 extension to also already declare a dependency on core Truth (or, failing that, to be using a build tool that was happy to let them rely on transitive dependencies). Here, we wouldn't expect a project that uses ProtoTruth to also declare a dependency on LiteProtoTruth. Again, though, that won't be a problem unless the user uses a build tool that is strict about transitive deps.

(Could we have Maven redirect things automatically? I think I saw that done for GWT, but maybe that can be done only for a change to groupId?)

The other option, of course, is to give up on modularizing the protobuf extensions. I don't know if you're modularizing them because you have a need for that or just because you want to be an even better citizen as you work to modularize the core.

Let me know if you have thoughts on the approach. I can think about it more before encouraging you to redo things yet again.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, the fact that we're not able to make any traction at modularizing the protobuf library is a real pain.

Putting aside the protobuf library for a minute, would this approach work for the truth library? protocolbuffers/protobuf#16133 (comment)

Or do you have a similar hierarchy problem as they do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, of course, we can't modularize "for real" until Protobuf does :(

I would really like to avoid duplicating all the existing classes, even those just in the protobuf extensions. Truth (especially ProtoTruth) doesn't get extended the way that Protobuf does, but it would still mean two parallel mini-ecosystems. I'd like to think that we can work out the merging of the two protobuf artifacts if it comes to that.


import static com.google.common.base.Strings.lenientFormat;
import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.extensions.proto.Platform.getTrimmedToString;
import static com.google.common.truth.extensions.liteproto.Platform.getTrimmedToString;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;
import com.google.common.truth.extensions.liteproto.internal.LiteProtoSubjectAccess;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.j2objc.annotations.J2ObjCIncompatible;
import com.google.protobuf.MessageLite;
Expand All @@ -44,6 +45,16 @@
@NullMarked
public class LiteProtoSubject extends Subject {

// Static initializer to register SharedSecrets accessor
static {
LiteProtoSubjectAccess.setAccessor(new LiteProtoSubjectAccess.Accessor() {
@Override
public String getCustomStringRepresentation(LiteProtoSubject subject) {
return subject.actualCustomStringRepresentationForProtoPackageMembersToCall();
}
});
}

/**
* Returns a {@code Subject.Factory} for {@link MessageLite} subjects which you can use to assert
* things about Lite Protobuf properties.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.google.common.truth.extensions.proto;
package com.google.common.truth.extensions.liteproto;

import static com.google.common.truth.Truth.assertAbout;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.google.common.truth.extensions.proto;
package com.google.common.truth.extensions.liteproto;

import static com.google.common.base.Strings.lenientFormat;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2025 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.common.truth.extensions.liteproto.internal;

import com.google.common.truth.extensions.liteproto.LiteProtoSubject;

/**
* Provides access to package-private methods in LiteProtoSubject for use by
* other Truth extensions.
*
* <p><b>This is an internal API and should not be used by client code.</b>
* This class may be removed or changed at any time without notice.
*/
public final class LiteProtoSubjectAccess {

/**
* Accessor interface for LiteProtoSubject internal methods.
* Set by LiteProtoSubject during class initialization.
*/
public interface Accessor {
String getCustomStringRepresentation(LiteProtoSubject subject);
}

private static Accessor accessor;

/**
* Sets the accessor. This is called by LiteProtoSubject during class initialization.
*
* @param accessor the accessor implementation
* @throws IllegalStateException if the accessor is already set
*/
public static void setAccessor(Accessor accessor) {
if (LiteProtoSubjectAccess.accessor != null) {
throw new IllegalStateException("Accessor already set");
}
LiteProtoSubjectAccess.accessor = accessor;
}

/**
* Gets the custom string representation for the given LiteProtoSubject.
*
* @param subject the LiteProtoSubject instance
* @return the custom string representation
* @throws IllegalStateException if no accessor has been set
*/
public static String getCustomStringRepresentation(LiteProtoSubject subject) {
if (accessor == null) {
throw new IllegalStateException("No accessor set");
}
return accessor.getCustomStringRepresentation(subject);
}

private LiteProtoSubjectAccess() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2016 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Custom subjects for testing <a href="https://developers.google.com/protocol-buffers/">Protocol
* Buffer Lite</a> instances.
*
* <p>This package provides Truth extensions specifically for protobuf-lite, which is a
* lighter-weight version of Protocol Buffers with reduced functionality but smaller
* binary size and better performance for resource-constrained environments.
*
* <p>This package is a part of the open-source <a href="https://github.com/google/truth">Truth</a>
* project.
*/
@CheckReturnValue
package com.google.common.truth.extensions.liteproto;

import com.google.errorprone.annotations.CheckReturnValue;
30 changes: 30 additions & 0 deletions extensions/liteproto/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

module com.google.truth.extensions.liteproto {
requires com.google.truth;
requires com.google.common;
requires java.compiler;

requires static org.jspecify;
requires static com.google.errorprone.annotations;
requires static protobuf.lite;
requires static auto.value.annotations;
requires static com.google.j2objc.annotations;

exports com.google.common.truth.extensions.liteproto;
exports com.google.common.truth.extensions.liteproto.internal to com.google.truth.extensions.proto;
}
Loading