Add robot comments creation and retrieval to the test api

We chose to mostly duplicate code rather than reuse code from
comments, to allow separation between robot and human comments.

Change-Id: Ieaa18566d4bd42bb0b6dfc70b0363eac4e7e32db
This commit is contained in:
Gal Paikin
2020-09-09 11:01:13 +03:00
parent b7c0786373
commit 83da3b1e25
17 changed files with 1139 additions and 89 deletions

View File

@@ -35,6 +35,7 @@ import com.google.gerrit.acceptance.testsuite.change.ChangeOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerCommentOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerDraftCommentOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerPatchsetOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerRobotCommentOperationsImpl;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
@@ -515,6 +516,7 @@ public class GerritServer implements AutoCloseable {
factory(PerPatchsetOperationsImpl.Factory.class);
factory(PerCommentOperationsImpl.Factory.class);
factory(PerDraftCommentOperationsImpl.Factory.class);
factory(PerRobotCommentOperationsImpl.Factory.class);
factory(PushOneCommit.Factory.class);
install(InProcessProtocol.module());
install(new NoSshModule());

View File

@@ -127,5 +127,13 @@ public interface ChangeOperations {
* @return an aggregation of operations on a specific draft comment
*/
PerDraftCommentOperations draftComment(String commentUuid);
/**
* Starts the fluent chain for querying or modifying a robot comment. Please see the methods of
* {@link PerRobotCommentOperations} for details on possible operations.
*
* @return an aggregation of operations on a specific robot comment
*/
PerRobotCommentOperations robotComment(String commentUuid);
}
}

View File

@@ -87,6 +87,7 @@ public class ChangeOperationsImpl implements ChangeOperations {
private final PerPatchsetOperationsImpl.Factory perPatchsetOperationsFactory;
private final PerCommentOperationsImpl.Factory perCommentOperationsFactory;
private final PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory;
private final PerRobotCommentOperationsImpl.Factory perRobotCommentOperationsFactory;
@Inject
public ChangeOperationsImpl(
@@ -102,7 +103,8 @@ public class ChangeOperationsImpl implements ChangeOperations {
ChangeFinder changeFinder,
PerPatchsetOperationsImpl.Factory perPatchsetOperationsFactory,
PerCommentOperationsImpl.Factory perCommentOperationsFactory,
PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory) {
PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory,
PerRobotCommentOperationsImpl.Factory perRobotCommentOperationsFactory) {
this.seq = seq;
this.changeInserterFactory = changeInserterFactory;
this.patchsetInserterFactory = patchsetInserterFactory;
@@ -116,6 +118,7 @@ public class ChangeOperationsImpl implements ChangeOperations {
this.perPatchsetOperationsFactory = perPatchsetOperationsFactory;
this.perCommentOperationsFactory = perCommentOperationsFactory;
this.perDraftCommentOperationsFactory = perDraftCommentOperationsFactory;
this.perRobotCommentOperationsFactory = perRobotCommentOperationsFactory;
}
@Override
@@ -555,5 +558,11 @@ public class ChangeOperationsImpl implements ChangeOperations {
ChangeNotes changeNotes = getChangeNotes();
return perDraftCommentOperationsFactory.create(changeNotes, commentUuid);
}
@Override
public PerRobotCommentOperations robotComment(String commentUuid) {
ChangeNotes changeNotes = getChangeNotes();
return perRobotCommentOperationsFactory.create(changeNotes, commentUuid);
}
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
/**
* Marks the commit that contains the comment (also known as side). Used by {@link
* TestCommentCreation} and {@link TestRobotCommentCreation}.
*/
enum CommentSide {
PATCHSET_COMMIT(1),
AUTO_MERGE_COMMIT(0),
PARENT_COMMIT(-1),
SECOND_PARENT_COMMIT(-2);
private final short numericSide;
CommentSide(int numericSide) {
this.numericSide = (short) numericSide;
}
public short getNumericSide() {
return numericSide;
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
import java.util.function.Function;
/**
* Builder for the file specification of line/range comments. Used by {@link TestCommentCreation}
* and {@link TestRobotCommentCreation}.
*/
public class FileBuilder<T> {
private final Function<String, T> nextStepProvider;
public FileBuilder(Function<String, T> nextStepProvider) {
this.nextStepProvider = nextStepProvider;
}
/** File on which the comment should be added. */
public T ofFile(String file) {
return nextStepProvider.apply(file);
}
}

View File

@@ -67,4 +67,25 @@ public interface PerPatchsetOperations {
* @return builder to create a new comment
*/
TestCommentCreation.Builder newDraftComment();
/**
* Starts the fluent chain to create a new robot comment. The returned builder can be used to
* specify the attributes of the robot comment. To create the robot comment for real, {@link
* TestRobotCommentCreation.Builder#create()} must be called.
*
* <p>Example:
*
* <pre>
* String createdRobotCommentUuid = changeOperations
* .change(changeId)
* .currentPatchset()
* .newRobotComment()
* .onLine(2)
* .ofFile("file1")
* .create();
* </pre>
*
* @return builder to create a new comment
*/
TestRobotCommentCreation.Builder newRobotComment();
}

View File

@@ -16,13 +16,13 @@ package com.google.gerrit.acceptance.testsuite.change;
import static com.google.gerrit.server.CommentsUtil.setCommentCommitId;
import com.google.gerrit.acceptance.testsuite.change.TestCommentCreation.CommentSide;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Comment.Status;
import com.google.gerrit.entities.HumanComment;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -101,6 +101,11 @@ public class PerPatchsetOperationsImpl implements PerPatchsetOperations {
return TestCommentCreation.builder(this::createComment, Status.DRAFT);
}
@Override
public TestRobotCommentCreation.Builder newRobotComment() {
return TestRobotCommentCreation.builder(this::createRobotComment);
}
private String createComment(TestCommentCreation commentCreation)
throws IOException, RestApiException, UpdateException {
Project.NameKey project = changeNotes.getProjectName();
@@ -126,6 +131,33 @@ public class PerPatchsetOperationsImpl implements PerPatchsetOperations {
return userFactory.create(authorId);
}
/**
* Both this and {@code toEntitiesCommentRange} is needed since there are two Comment.Range
* entities, in different packages: {@code com.google.gerrit.entities.Comment.Range}, and {@code
* com.google.gerrit.extensions.Comment.Range}
*/
private static Comment.Range toCommentRange(TestRange range) {
Comment.Range commentRange = new Range();
commentRange.startLine = range.start().line();
commentRange.startCharacter = range.start().charOffset();
commentRange.endLine = range.end().line();
commentRange.endCharacter = range.end().charOffset();
return commentRange;
}
/**
* Both this and {@code toCommentRange} is needed since there are two Comment.Range entities, in
* different packages: {@code com.google.gerrit.entities.Comment.Range}, and {@code
* com.google.gerrit.extensions.Comment.Range}
*/
private static com.google.gerrit.entities.Comment.Range toEntitiesCommentRange(TestRange range) {
return new com.google.gerrit.entities.Comment.Range(
range.start().line(),
range.start().charOffset(),
range.end().line(),
range.end().charOffset());
}
private class CommentAdditionOp implements BatchUpdateOp {
private String createdCommentUuid;
private final TestCommentCreation commentCreation;
@@ -173,7 +205,7 @@ public class PerPatchsetOperationsImpl implements PerPatchsetOperations {
// Specification of range trumps explicit line specification.
commentCreation
.range()
.map(this::toCommentRange)
.map(PerPatchsetOperationsImpl::toCommentRange)
.ifPresent(range -> newComment.setLineNbrAndRange(null, range));
setCommentCommitId(
@@ -183,14 +215,94 @@ public class PerPatchsetOperationsImpl implements PerPatchsetOperations {
changeNotes.getPatchSets().get(patchsetId));
return newComment;
}
}
private Comment.Range toCommentRange(TestRange range) {
Comment.Range commentRange = new Range();
commentRange.startLine = range.start().line();
commentRange.startCharacter = range.start().charOffset();
commentRange.endLine = range.end().line();
commentRange.endCharacter = range.end().charOffset();
return commentRange;
private String createRobotComment(TestRobotCommentCreation robotCommentCreation)
throws IOException, RestApiException, UpdateException {
Project.NameKey project = changeNotes.getProjectName();
try (Repository repository = repositoryManager.openRepository(project);
ObjectInserter objectInserter = repository.newObjectInserter();
RevWalk revWalk = new RevWalk(objectInserter.newReader())) {
Timestamp now = TimeUtil.nowTs();
IdentifiedUser author = getAuthor(robotCommentCreation);
RobotCommentAdditionOp robotCommentAdditionOp =
new RobotCommentAdditionOp(robotCommentCreation);
try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, author, now)) {
batchUpdate.setRepository(repository, revWalk, objectInserter);
batchUpdate.addOp(changeNotes.getChangeId(), robotCommentAdditionOp);
batchUpdate.execute();
}
return robotCommentAdditionOp.createdRobotCommentUuid;
}
}
private IdentifiedUser getAuthor(TestRobotCommentCreation robotCommentCreation) {
Account.Id authorId = robotCommentCreation.author().orElse(changeNotes.getChange().getOwner());
return userFactory.create(authorId);
}
private class RobotCommentAdditionOp implements BatchUpdateOp {
private String createdRobotCommentUuid;
private final TestRobotCommentCreation robotCommentCreation;
public RobotCommentAdditionOp(TestRobotCommentCreation robotCommentCreation) {
this.robotCommentCreation = robotCommentCreation;
}
@Override
public boolean updateChange(ChangeContext context) throws Exception {
RobotComment robotComment = toNewRobotComment(context, robotCommentCreation);
ChangeUpdate changeUpdate = context.getUpdate(patchsetId);
changeUpdate.putRobotComment(robotComment);
// For robot comments, only the tag set on the ChangeUpdate (and not on the RobotComment)
// matters.
robotCommentCreation.tag().ifPresent(changeUpdate::setTag);
createdRobotCommentUuid = robotComment.key.uuid;
return true;
}
private RobotComment toNewRobotComment(
ChangeContext context, TestRobotCommentCreation robotCommentCreation)
throws PatchListNotAvailableException {
String message = robotCommentCreation.message().orElse("The text of a test robot comment.");
String filePath = robotCommentCreation.file().orElse(Patch.PATCHSET_LEVEL);
short side = robotCommentCreation.side().orElse(CommentSide.PATCHSET_COMMIT).getNumericSide();
String robotId = robotCommentCreation.robotId().orElse("robot");
String robotRunId = robotCommentCreation.robotId().orElse("1");
RobotComment newRobotComment =
commentsUtil.newRobotComment(
context, filePath, patchsetId, side, message, robotId, robotRunId);
// TODO(paiking): This should not be needed, as the tag only matters in ChangeUpdate.
robotCommentCreation.tag().ifPresent(tag -> newRobotComment.tag = tag);
robotCommentCreation.line().ifPresent(line -> newRobotComment.setLineNbrAndRange(line, null));
// Specification of range trumps explicit line specification.
robotCommentCreation
.range()
.map(PerPatchsetOperationsImpl::toCommentRange)
.ifPresent(range -> newRobotComment.setLineNbrAndRange(null, range));
robotCommentCreation
.unresolved()
.ifPresent(unresolved -> newRobotComment.unresolved = unresolved);
robotCommentCreation
.parentUuid()
.ifPresent(parentUuid -> newRobotComment.parentUuid = parentUuid);
robotCommentCreation.url().ifPresent(url -> newRobotComment.url = url);
if (!robotCommentCreation.properties().isEmpty()) {
newRobotComment.properties = robotCommentCreation.properties();
}
setCommentCommitId(
newRobotComment,
patchListCache,
context.getChange(),
changeNotes.getPatchSets().get(patchsetId));
return newRobotComment;
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
/** An aggregation of methods on a specific, robot comment. */
public interface PerRobotCommentOperations {
/**
* Retrieves the robot comment.
*
* <p><strong>Note:</strong> This call will fail with an exception if the requested comment
* doesn't exist or if it is a comment of another type.
*
* @return the corresponding {@code TestRobotComment}
*/
TestRobotComment get();
}

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
import static com.google.common.collect.MoreCollectors.onlyElement;
import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
/**
* The implementation of {@link PerRobotCommentOperations}.
*
* <p>There is only one implementation of {@link PerRobotCommentOperations}. Nevertheless, we keep
* the separation between interface and implementation to enhance clarity.
*/
public class PerRobotCommentOperationsImpl implements PerRobotCommentOperations {
private final CommentsUtil commentsUtil;
private final ChangeNotes changeNotes;
private final String commentUuid;
public interface Factory {
PerRobotCommentOperationsImpl create(ChangeNotes changeNotes, String commentUuid);
}
@Inject
public PerRobotCommentOperationsImpl(
CommentsUtil commentsUtil, @Assisted ChangeNotes changeNotes, @Assisted String commentUuid) {
this.commentsUtil = commentsUtil;
this.changeNotes = changeNotes;
this.commentUuid = commentUuid;
}
@Override
public TestRobotComment get() {
RobotComment comment =
commentsUtil.robotCommentsByChange(changeNotes).stream()
.filter(foundComment -> foundComment.key.uuid.equals(commentUuid))
.collect(onlyElement());
return toTestRobotComment(comment);
}
static TestRobotComment toTestRobotComment(RobotComment robotComment) {
return TestRobotComment.builder()
.uuid(robotComment.key.uuid)
.parentUuid(robotComment.parentUuid)
.build();
}
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
import java.util.function.IntFunction;
/**
* Builder to simplify a position specification. Used by {@link TestCommentCreation} and {@link
* TestRobotCommentCreation}.
*/
public class PositionBuilder<T> {
private final IntFunction<T> nextStepProvider;
public PositionBuilder(IntFunction<T> nextStepProvider) {
this.nextStepProvider = nextStepProvider;
}
/** Character offset within the line. A value of 0 indicates the beginning of the line. */
public T charOffset(int characterOffset) {
return nextStepProvider.apply(characterOffset);
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Builder for the end position of a range. Used by {@link TestCommentCreation} and {@link
* TestRobotCommentCreation}.
*/
public class StartAwarePositionBuilder<T> {
private final TestRange.Builder testRangeBuilder;
private final Consumer<TestRange> rangeConsumer;
private final Function<String, T> fileFunction;
public StartAwarePositionBuilder(
TestRange.Builder testRangeBuilder,
Consumer<TestRange> rangeConsumer,
Function<String, T> fileFunction) {
this.testRangeBuilder = testRangeBuilder;
this.rangeConsumer = rangeConsumer;
this.fileFunction = fileFunction;
}
/** Line of the end position of the range. */
public PositionBuilder<FileBuilder<T>> toLine(int endLine) {
return new PositionBuilder<>(
endCharOffset -> {
TestRange.Position end =
TestRange.Position.builder().line(endLine).charOffset(endCharOffset).build();
TestRange range = testRangeBuilder.setEnd(end).build();
rangeConsumer.accept(range);
return new FileBuilder<T>(fileFunction);
});
}
}

View File

@@ -22,10 +22,12 @@ import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.Patch;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.IntFunction;
/** Attributes of the comment. If not provided, arbitrary values will be used. */
/**
* Attributes of the human comment. If not provided, arbitrary values will be used. This class is
* very similar to {@link TestRobotCommentCreation} to allow separation between robot and human
* comments.
*/
@AutoValue
public abstract class TestCommentCreation {
@@ -51,7 +53,7 @@ public abstract class TestCommentCreation {
abstract ThrowingFunction<TestCommentCreation, String> commentCreator();
public static TestCommentCreation.Builder builder(
public static Builder builder(
ThrowingFunction<TestCommentCreation, String> commentCreator, Comment.Status commentStatus) {
return new AutoValue_TestCommentCreation.Builder()
.commentCreator(commentCreator)
@@ -82,8 +84,8 @@ public abstract class TestCommentCreation {
* Starts the fluent change to create a line comment. The line comment will be at the indicated
* line. Lines start with 1.
*/
public FileBuilder onLine(int line) {
return new FileBuilder(file -> file(file).line(line).range(null));
public FileBuilder<Builder> onLine(int line) {
return new FileBuilder<>(file -> file(file).line(line).range(null));
}
/**
@@ -91,12 +93,12 @@ public abstract class TestCommentCreation {
* Lines start at 1. The start position (line, charOffset) is inclusive, the end position (line,
* charOffset) is exclusive.
*/
public PositionBuilder<StartAwarePositionBuilder> fromLine(int startLine) {
public PositionBuilder<StartAwarePositionBuilder<Builder>> fromLine(int startLine) {
return new PositionBuilder<>(
startCharOffset -> {
Position start = Position.builder().line(startLine).charOffset(startCharOffset).build();
TestRange.Builder testRangeBuilder = TestRange.builder().setStart(start);
return new StartAwarePositionBuilder(this, testRangeBuilder);
return new StartAwarePositionBuilder<>(testRangeBuilder, this::range, this::file);
});
}
@@ -179,8 +181,7 @@ public abstract class TestCommentCreation {
*/
abstract Builder status(Comment.Status value);
abstract TestCommentCreation.Builder commentCreator(
ThrowingFunction<TestCommentCreation, String> commentCreator);
abstract Builder commentCreator(ThrowingFunction<TestCommentCreation, String> commentCreator);
abstract TestCommentCreation autoBuild();
@@ -194,72 +195,4 @@ public abstract class TestCommentCreation {
return commentCreation.commentCreator().applyAndThrowSilently(commentCreation);
}
}
/** Builder for the file specification of line/range comments. */
public static class FileBuilder {
private final Function<String, Builder> nextStepProvider;
private FileBuilder(Function<String, Builder> nextStepProvider) {
this.nextStepProvider = nextStepProvider;
}
/** File on which the comment should be added. */
public Builder ofFile(String file) {
return nextStepProvider.apply(file);
}
}
/** Builder to simplify a position specification. */
public static class PositionBuilder<T> {
private final IntFunction<T> nextStepProvider;
private PositionBuilder(IntFunction<T> nextStepProvider) {
this.nextStepProvider = nextStepProvider;
}
/** Character offset within the line. A value of 0 indicates the beginning of the line. */
public T charOffset(int characterOffset) {
return nextStepProvider.apply(characterOffset);
}
}
/** Builder for the end position of a range. */
public static class StartAwarePositionBuilder {
private final TestCommentCreation.Builder testCommentCreationBuilder;
private final TestRange.Builder testRangeBuilder;
private StartAwarePositionBuilder(
Builder testCommentCreationBuilder, TestRange.Builder testRangeBuilder) {
this.testCommentCreationBuilder = testCommentCreationBuilder;
this.testRangeBuilder = testRangeBuilder;
}
/** Line of the end position of the range. */
public PositionBuilder<FileBuilder> toLine(int endLine) {
return new PositionBuilder<>(
endCharOffset -> {
Position end = Position.builder().line(endLine).charOffset(endCharOffset).build();
TestRange range = testRangeBuilder.setEnd(end).build();
testCommentCreationBuilder.range(range);
return new FileBuilder(testCommentCreationBuilder::file);
});
}
}
enum CommentSide {
PATCHSET_COMMIT(1),
AUTO_MERGE_COMMIT(0),
PARENT_COMMIT(-1),
SECOND_PARENT_COMMIT(-2);
private final short numericSide;
CommentSide(int numericSide) {
this.numericSide = (short) numericSide;
}
public short getNumericSide() {
return numericSide;
}
}
}

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
import com.google.auto.value.AutoValue;
import com.google.gerrit.common.Nullable;
import java.util.Optional;
/** Representation of a robot comment used for testing purposes. */
@AutoValue
public abstract class TestRobotComment {
/** The UUID of the comment. Should be unique. */
public abstract String uuid();
/** UUID of another comment to which this comment is a reply. */
public abstract Optional<String> parentUuid();
static Builder builder() {
return new AutoValue_TestRobotComment.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder uuid(String uuid);
abstract Builder parentUuid(@Nullable String parentUuid);
abstract TestRobotComment build();
}
}

View File

@@ -0,0 +1,214 @@
// Copyright (C) 2020 The Android Open Source Project
//
// 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.gerrit.acceptance.testsuite.change;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
import com.google.gerrit.acceptance.testsuite.change.TestRange.Position;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Patch;
import java.util.Map;
import java.util.Optional;
/**
* Attributes of the robot comment. If not provided, arbitrary values will be used. This class is
* very similar to {@link TestCommentCreation} to allow separation between robot and human comments.
*/
@AutoValue
public abstract class TestRobotCommentCreation {
public abstract Optional<String> message();
public abstract Optional<String> file();
public abstract Optional<Integer> line();
public abstract Optional<TestRange> range();
public abstract Optional<CommentSide> side();
public abstract Optional<Boolean> unresolved();
public abstract Optional<String> parentUuid();
public abstract Optional<String> tag();
public abstract Optional<Account.Id> author();
public abstract Optional<String> robotId();
public abstract Optional<String> robotRunId();
public abstract Optional<String> url();
public abstract ImmutableMap<String, String> properties();
abstract ThrowingFunction<TestRobotCommentCreation, String> commentCreator();
public static Builder builder(ThrowingFunction<TestRobotCommentCreation, String> commentCreator) {
return new AutoValue_TestRobotCommentCreation.Builder().commentCreator(commentCreator);
}
@AutoValue.Builder
public abstract static class Builder {
public Builder noMessage() {
return message("");
}
/** Message text of the comment. */
public abstract Builder message(String message);
/** Indicates a patchset-level comment. */
public Builder onPatchsetLevel() {
return file(Patch.PATCHSET_LEVEL);
}
/** Indicates a file comment. The comment will be on the specified file. */
public Builder onFileLevelOf(String filePath) {
return file(filePath).line(null).range(null);
}
/**
* Starts the fluent change to create a line comment. The line comment will be at the indicated
* line. Lines start with 1.
*/
public FileBuilder<Builder> onLine(int line) {
return new FileBuilder<>(file -> file(file).line(line).range(null));
}
/**
* Starts the fluent chain to create a range comment. The range begins at the specified line.
* Lines start at 1. The start position (line, charOffset) is inclusive, the end position (line,
* charOffset) is exclusive.
*/
public PositionBuilder<StartAwarePositionBuilder<Builder>> fromLine(int startLine) {
return new PositionBuilder<>(
startCharOffset -> {
Position start = Position.builder().line(startLine).charOffset(startCharOffset).build();
TestRange.Builder testRangeBuilder = TestRange.builder().setStart(start);
return new StartAwarePositionBuilder<>(testRangeBuilder, this::range, this::file);
});
}
/** File on which the comment should be added. */
abstract Builder file(String filePath);
/** Line on which the comment should be added. */
abstract Builder line(@Nullable Integer line);
/** Range on which the comment should be added. */
abstract Builder range(@Nullable TestRange range);
/**
* Indicates that the comment refers to a file, line, range, ... in the commit of the patchset.
*
* <p>On the UI, such comments are shown on the right side of a diff view when a diff against
* base is selected. See {@link #onParentCommit()} for comments shown on the left side.
*/
public Builder onPatchsetCommit() {
return side(CommentSide.PATCHSET_COMMIT);
}
/**
* Indicates that the comment refers to a file, line, range, ... in the parent commit of the
* patchset.
*
* <p>On the UI, such comments are shown on the left side of a diff view when a diff against
* base is selected. See {@link #onPatchsetCommit()} for comments shown on the right side.
*
* <p>For merge commits, this indicates the first parent commit.
*/
public Builder onParentCommit() {
return side(CommentSide.PARENT_COMMIT);
}
/** Like {@link #onParentCommit()} but for the second parent of a merge commit. */
public Builder onSecondParentCommit() {
return side(CommentSide.SECOND_PARENT_COMMIT);
}
/**
* Like {@link #onParentCommit()} but for the AutoMerge commit created from the parents of a
* merge commit.
*/
public Builder onAutoMergeCommit() {
return side(CommentSide.AUTO_MERGE_COMMIT);
}
abstract Builder side(CommentSide side);
/** Indicates a resolved comment. */
public Builder resolved() {
return unresolved(false);
}
/** Indicates an unresolved comment. */
public Builder unresolved() {
return unresolved(true);
}
abstract Builder unresolved(boolean unresolved);
/**
* UUID of another comment to which this comment is a reply. This comment must have similar
* attributes (e.g. file, line, side) as the parent comment. The parent comment must be a
* published comment.
*/
public abstract Builder parentUuid(String parentUuid);
/** Tag to attach to the comment. */
public abstract Builder tag(String value);
/** Author of the comment. Must be an existing user account. */
public abstract Builder author(Account.Id accountId);
/** Id of the robot that created the comment. */
public abstract Builder robotId(String robotId);
/** An ID of the run of the robot that created the comment. */
public abstract Builder robotRunId(String robotRunId);
/** Url for more information for the robot comment. */
public abstract Builder url(String url);
/** Robot specific properties as map that maps arbitrary keys to values. */
public abstract Builder properties(Map<String, String> properties);
abstract ImmutableMap.Builder<String, String> propertiesBuilder();
public Builder addProperty(String key, String value) {
propertiesBuilder().put(key, value);
return this;
}
abstract Builder commentCreator(
ThrowingFunction<TestRobotCommentCreation, String> commentCreator);
abstract TestRobotCommentCreation autoBuild();
/**
* Creates the robot comment.
*
* @return the UUID of the created robot comment
*/
public String create() {
TestRobotCommentCreation commentCreation = autoBuild();
return commentCreation.commentCreator().applyAndThrowSilently(commentCreation);
}
}
}

View File

@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertAbout;
import static com.google.gerrit.truth.ListSubject.elements;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.MapSubject;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.FixSuggestionInfo;
@@ -59,6 +60,26 @@ public class RobotCommentInfoSubject extends Subject {
return check("path").that(robotCommentInfo.path);
}
public StringSubject robotId() {
isNotNull();
return check("robotId").that(robotCommentInfo.robotId);
}
public StringSubject robotRunId() {
isNotNull();
return check("robotRunId").that(robotCommentInfo.robotRunId);
}
public StringSubject url() {
isNotNull();
return check("url").that(robotCommentInfo.url);
}
public MapSubject properties() {
isNotNull();
return check("property").that(robotCommentInfo.properties);
}
public FixSuggestionInfoSubject onlyFixSuggestion() {
return fixSuggestions().onlyElement();
}