Skip to content

sync

Synchronize coverage links bidirectionally between production code and tests.

Synopsis

bash
testlink sync [options]

Description

The sync command maintains bidirectional links between production and test code:

Production → Test (forward sync):

  • Reads #[TestedBy] attributes from production code
  • Adds ->linksAndCovers() (Pest) or #[LinksAndCovers] (PHPUnit) to test files
  • Adds @see tags to production code for IDE navigation

Test → Production (reverse sync):

  • Reads ->linksAndCovers() or #[LinksAndCovers] from test files
  • Adds #[TestedBy] attributes to production methods

This ensures both directions stay synchronized regardless of which side you add links first.

Options

OptionDescription
--dry-runPreview changes without modifying files
--pruneRemove orphaned links
--link-onlyAdd links without coverage (uses ->links() instead of ->linksAndCovers())
--forceRequired with --prune to confirm destructive operation
--verbose, -vShow detailed information
--path=<path>Filter by directory or file path

Examples

Preview sync

bash
./vendor/bin/testlink sync --dry-run

Output:

  Syncing Coverage Links
  ──────────────────────
  Running in dry-run mode. No files will be modified.

  Would modify test files
    ✓ tests/Unit/UserServiceTest.php
      + linksAndCovers(UserService::class.'::create')

  Would add @see tags to production
    ✓ UserService::create
      + @see UserServiceTest::test_creates_user

  Would add #[TestedBy] to production
    ✓ OrderService::process
      + #[TestedBy(OrderServiceTest::class, 'test_processes_order')]

  Summary (dry-run)
  ─────────────────
    Files modified:           3
    Files pruned:             0
    @see tags added:          1
    @see tags removed:        0
    #[TestedBy] added:        1

  ✓ Dry-run complete. Use --no-dry-run to apply.

Apply sync

bash
./vendor/bin/testlink sync

Output:

  Syncing Coverage Links
  ──────────────────────

  Modified Files
    ✓ tests/Unit/UserServiceTest.php (2 changes)
    ✓ src/Services/OrderService.php (1 change)

  Summary
  ───────
    Files modified:           2
    Files pruned:             0
    @see tags added:          1
    @see tags removed:        0
    #[TestedBy] added:        1

  ✓ Sync complete.

Sync with pruning

bash
./vendor/bin/testlink sync --prune --force

Output:

  Syncing Coverage Links
  ──────────────────────

  Modified Files
    ✓ tests/UserServiceTest.php
      + linksAndCovers(UserService::class.'::create')

  Pruned from Files
    − tests/OldTest.php
      - linksAndCovers(DeletedService::class.'::method')

  Summary
  ───────
    Files modified:           1
    Files pruned:             1
    @see tags added:          0
    @see tags removed:        2
    #[TestedBy] added:        0

  ✓ Sync complete.

Filter by path

bash
./vendor/bin/testlink sync --path=src/Services

What Gets Synced

Forward Sync: Production → Test

When production has #[TestedBy]:

php
class UserService
{
    #[TestedBy(UserServiceTest::class, 'test_creates_user')]
    public function create(): User { }
}

After sync, test file gets:

php
test('creates user', function () {
    // ...
})->linksAndCovers(UserService::class.'::create');

Reverse Sync: Test → Production

When test has a link but production doesn't have #[TestedBy]:

php
test('creates user', function () {
    // ...
})->linksAndCovers(UserService::class.'::create');

After sync, production gets:

php
use TestFlowLabs\TestingAttributes\TestedBy;

class UserService
{
    #[TestedBy(UserServiceTest::class, 'test_creates_user')]
    public function create(): User { }
}

@see Tags for IDE Navigation

Production methods always get @see tags for Cmd+Click navigation:

php
/**
 * @see \Tests\Unit\UserServiceTest::test_creates_user
 */
#[TestedBy(UserServiceTest::class, 'test_creates_user')]
public function create(): User { }

Pruning Behavior

With --prune --force, removes links that:

  • Point to non-existent classes
  • Point to non-existent methods
  • Point to removed tests

Example:

php
/**
 * @see \App\DeletedService::method    ← Removed (class doesn't exist)
 * @see \App\UserService::create       ← Kept
 */

Exit Codes

CodeMeaning
0Success
1Error (unable to sync)

Workflow

bash
# 1. Preview changes
./vendor/bin/testlink sync --dry-run

# 2. Apply changes
./vendor/bin/testlink sync

# 3. Validate result
./vendor/bin/testlink validate

With pruning

bash
# Preview sync and prune
./vendor/bin/testlink sync --dry-run --prune --force

# Apply
./vendor/bin/testlink sync --prune --force

Bidirectional Sync Explained

The sync command is truly bidirectional:

You add link heresync adds to
Production: #[TestedBy(...)]Test: linksAndCovers() or #[LinksAndCovers]
Test: linksAndCovers() or #[LinksAndCovers]Production: #[TestedBy(...)]

Both sides also get @see tags for IDE navigation.

This means you can start linking from either side:

  1. Add #[TestedBy] to production, run sync → test gets link
  2. Add linksAndCovers() to test, run sync → production gets #[TestedBy]

Released under the MIT License.