Skip to content

Commit

Permalink
Add recoverable review diff service (#988)
Browse files Browse the repository at this point in the history
* Add RecoverableReviewDiffService.php

* Add RecoverableReviewDiffService.php

* Add RecoverableReviewDiffService.php

* Add coverage
  • Loading branch information
frankdekker authored Jan 31, 2025
1 parent 265a426 commit 446942a
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 1 deletion.
4 changes: 3 additions & 1 deletion config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use DR\Review\Service\Git\GitRepositoryService;
use DR\Review\Service\Git\Review\ReviewDiffService\CacheableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\LockableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\RecoverableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\ReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\ReviewDiffServiceInterface;
use DR\Review\Service\Git\Review\Strategy\BasicCherryPickStrategy;
Expand Down Expand Up @@ -191,7 +192,8 @@
$services->set(HesitantCherryPickStrategy::class)->tag('review_diff_strategy', ['priority' => 10]);
$services->set('review.diff.service', ReviewDiffService::class)->arg('$reviewDiffStrategies', tagged_iterator('review_diff_strategy'));

$services->set('lock.review.diff.service', LockableReviewDiffService::class)->arg('$diffService', service('review.diff.service'));
$services->set('recoverable.review.diff.service', RecoverableReviewDiffService::class)->arg('$diffService', service('review.diff.service'));
$services->set('lock.review.diff.service', LockableReviewDiffService::class)->arg('$diffService', service('recoverable.review.diff.service'));
$services->set(ReviewDiffServiceInterface::class, CacheableReviewDiffService::class)->arg('$diffService', service('lock.review.diff.service'));
$services->set(ReviewRouter::class)->decorate('router')->args([service('.inner')]);
$services->set(CodeReviewFileService::class)->arg('$revisionCache', service(CacheInterface::class . ' $revisionCache'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

namespace DR\Review\Service\Git\Review\ReviewDiffService;

use DR\Review\Entity\Repository\Repository;
use DR\Review\Entity\Review\CodeReview;
use DR\Review\Service\Git\Review\FileDiffOptions;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Process\Exception\ProcessFailedException;

class RecoverableReviewDiffService implements ReviewDiffServiceInterface, LoggerAwareInterface
{
use LoggerAwareTrait;

public function __construct(private readonly ReviewDiffServiceInterface $diffService)
{
}

/**
* @inheritDoc
*/
public function getDiffForRevisions(Repository $repository, array $revisions, ?FileDiffOptions $options = null): array
{
return $this->diffService->getDiffForRevisions($repository, $revisions, $options);
}

/**
* The target branch of the review might be stale, try to reset it to the main branch if the diff fails.
* @inheritDoc
*/
public function getDiffForBranch(CodeReview $review, array $revisions, string $branchName, ?FileDiffOptions $options = null): array
{
try {
return $this->diffService->getDiffForBranch($review, $revisions, $branchName, $options);
} catch (ProcessFailedException $exception) {
if ($review->getTargetBranch() === $review->getRepository()->getMainBranchName()) {
throw $exception;
}

$this->logger?->notice('Failed to get diff for branch, trying to reset target branch', ['exception' => $exception]);
$review->setTargetBranch($review->getRepository()->getMainBranchName());
}

return $this->diffService->getDiffForBranch($review, $revisions, $branchName, $options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);

namespace DR\Review\Tests\Unit\Service\Git\Review\ReviewDiffService;

use DR\Review\Entity\Git\Diff\DiffComparePolicy;
use DR\Review\Entity\Repository\Repository;
use DR\Review\Entity\Review\CodeReview;
use DR\Review\Entity\Revision\Revision;
use DR\Review\Service\Git\Review\FileDiffOptions;
use DR\Review\Service\Git\Review\ReviewDiffService\RecoverableReviewDiffService;
use DR\Review\Service\Git\Review\ReviewDiffService\ReviewDiffServiceInterface;
use DR\Review\Tests\AbstractTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use stdClass;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Throwable;

#[CoversClass(RecoverableReviewDiffService::class)]
class RecoverableReviewDiffServiceTest extends AbstractTestCase
{
private ReviewDiffServiceInterface&MockObject $diffService;
private RecoverableReviewDiffService $service;

protected function setUp(): void
{
parent::setUp();
$this->diffService = $this->createMock(ReviewDiffServiceInterface::class);
$this->service = new RecoverableReviewDiffService($this->diffService);
}

/**
* @throws Throwable
*/
public function testGetDiffForRevisions(): void
{
$repository = new Repository();
$revisions = [new Revision()];
$options = new FileDiffOptions(5, DiffComparePolicy::ALL);

$this->diffService->expects(self::once())->method('getDiffForRevisions')->with($repository, $revisions, $options);

$this->service->getDiffForRevisions($repository, $revisions, $options);
}

/**
* @throws Throwable
*/
public function testGetDiffForBranchExpectTwoAttempts(): void
{
$callCount = new stdClass();
$callCount->count = 0;
$repository = (new Repository())->setMainBranchName('master');
$review = (new CodeReview())->setTargetBranch('foobar')->setRepository($repository);
$revisions = [new Revision()];
$options = new FileDiffOptions(5, DiffComparePolicy::ALL);
$branchName = 'branch';

$exception = $this->createMock(ProcessFailedException::class);

$this->diffService->expects(self::exactly(2))->method('getDiffForBranch')
->with($review, $revisions, $branchName, $options)
->willThrowException($exception);

$this->expectException(ProcessFailedException::class);
$this->service->getDiffForBranch($review, $revisions, $branchName, $options);
}

/**
* @throws Throwable
*/
public function testGetDiffForBranchExpectOneAttemptOnMasterBranch(): void
{
$callCount = new stdClass();
$callCount->count = 0;
$repository = (new Repository())->setMainBranchName('master');
$review = (new CodeReview())->setTargetBranch('master')->setRepository($repository);
$revisions = [new Revision()];
$options = new FileDiffOptions(5, DiffComparePolicy::ALL);
$branchName = 'branch';

$exception = $this->createMock(ProcessFailedException::class);

$this->diffService->expects(self::once())->method('getDiffForBranch')
->with($review, $revisions, $branchName, $options)
->willThrowException($exception);

$this->expectException(ProcessFailedException::class);
$this->service->getDiffForBranch($review, $revisions, $branchName, $options);
}
}

0 comments on commit 446942a

Please sign in to comment.