DiffEngine

repository·main·Indexed 42 days ago

https://github.com/verifytests/diffengine

A foundational component for Snapshot and Approval testing libraries that manages the launching and cleanup of various diff tools across Windows, macOS, and Linux. It supports a wide range of tools including AraxisMerge, BeyondCompare, Meld, KDiff3, and Visual Studio Code, and provides a native macOS renderer for visual diffing.

Tokens
25K
Snippets
67
Records
141
Agent score
53%

What's inside DiffEngine

  1. Review moves and deletes via DiffEngineTray

    main

    If DiffEngineTray is running, the viewer will also list pending file moves and deletes in the same queue as snapshots.

    • Moves: Shows the received file against the committed one. Use Accept move or Discard.
    • Deletes: Shows the file's content against nothing. Use Accept delete or Discard.
    • Bulk Actions: Accept all will sweep through snapshots, moves, and deletes, but will still skip conflicted snapshots.
  2. Understand the default Diff Tool resolution order

    main

    DiffEngine attempts to resolve diff tools in a specific priority order. If multiple tools are installed on your system, DiffEngine will use the first one it finds in its internal priority list. The default order is as follows:

    • MsWordDiff (Windows, requires Word)
    • MsExcelDiff (Windows, requires Excel)
    • BeyondCompare (Windows/OSX/Linux)
    • P4Merge (Windows/OSX/Linux)
    • Kaleidoscope (OSX)
    • DeltaWalker (Windows/OSX)
    • WinMerge (Windows)
    • TortoiseMerge (Windows)
    • TortoiseGitMerge (Windows)
    • TortoiseGitIDiff (Windows)
    • TortoiseIDiff (Windows)
    • KDiff3 (Windows/OSX)
    • TkDiff (OSX)
    • Guiffy (Windows/OSX)
    • ExamDiff (Windows)
    • Diffinity (Windows)
    • Rider (Windows/OSX/Linux)
    • Vim (Windows/OSX)
    • Neovim (Windows/OSX/Linux)
    • AraxisMerge (Windows/OSX)
    • Meld (Windows/OSX/Linux)
    • SublimeMerge (Windows/OSX/Linux)
    • VisualStudioCode (Windows/OSX/Linux)
    • VisualStudio (Windows)
    • Cursor (Windows/OSX/Linux)
    • DiffEngineViewer (Windows/OSX/Linux)
  3. Understand tool resolution order

    main

    When using DiffRunner.LaunchAsync(tempFile, targetFile), the engine selects a tool based on the following priority:

    1. Newest First: New tools are added to the top of the resolution list. The most recently added tool that supports the file types will be the one that resolves and launches.
    2. Explicit Launch: If you want to bypass the automatic resolution logic, you can use the specific DiffTool instance returned by AddTool* methods and pass it directly to DiffRunner.LaunchAsync.
    // Automatic resolution (uses the most recently added compatible tool)
    await DiffRunner.LaunchAsync(tempFile, targetFile);
    
    // Explicit launch (uses the specific tool instance)
    var resolvedTool = DiffTools.AddToolBasedOn(...);
    await DiffRunner.LaunchAsync(resolvedTool!, "PathToTempFile", "PathToTargetFile");
  4. How images are compared in DiffEngineViewer

    main

    DiffEngineViewer compares image files (.png, .jpg, .jpeg, .gif, .bmp, .webp, and .ico) as pictures rather than text.

    Key behaviors:

    • Comparison Logic: The status line indicates if images are identical, images differ, or only <file> exists (for new snapshots).
    • Metadata Display: Each pane lists the format, pixel dimensions, and byte count. Properties are marked as unchanged or modified based on the comparison.
    • Rendering: Images are drawn in the pane on a checkerboard background (to show transparency) and are fitted to the space without being enlarged beyond their original size.
    • Format Support: Rendering depends on the platform's native toolkit:
      • Windows (GDI+): .png, .jpg, .jpeg, .gif, .bmp, .ico
      • macOS (ImageIO): All seven supported formats.
      • Linux (raylib): .png, .jpg, .jpeg, .gif, .bmp
    • Note: Comparison is based on file extension, not content. A file with a .png extension that contains non-PNG data will report its format as unrecognized rather than attempting to render bytes as text.
  5. How image rendering and caching work in the macOS renderer

    main

    The macOS renderer uses ImageIO instead of NSImage to ensure it retrieves the file's actual pixels rather than a screen-sized representation.

    Caching Logic:

    • Images are cached and invalidated based on write time and file length. This matches the freshness test used by the managed queue poller.
    • If ImageIO fails to read a file, the failure is remembered and nothing is drawn, though the property rows above the image will still display the comparison data.

    Capturing Bitmaps:

    • deview_capture draws into its own bitmap context rather than using the existing view. This ensures that scale, color space, and font smoothing/subpixel settings are pinned, preventing issues where the capture would otherwise inherit the window's backing scale. This also allows capture to function without a window server, making snapshot tests more stable.
  6. How C# inline snapshots are written

    main

    When DiffEngine generates inline snapshots for C#, it follows specific rules for literal formatting based on the content and the source file's style:

    • Form: Single-line content uses regular string literals (escaping \, ", control characters, and \uXXXX). Multi-line content uses raw string literals ("""). Empty content is rendered as "".
    • Delimiter: Uses three quotes (""") or one more than the longest run of quotes in the content (e.g., """" if the content contains """).
    • Placement: Raw literals are placed on the line below the opening parenthesis, aligning the opening delimiter with the content. Regular literals stay within the argument list.
    • Indentation: Uses the call site's leading whitespace plus one level. The indentation level is determined by the file's most common whitespace pattern (e.g., 2 spaces or 4 spaces).
    • Line Endings: Uses the file's dominant line ending to ensure cross-platform compatibility.

    To produce identical text in your own tools, use the public CsStringLiteral.Render method.

    // single line content
    await Verify(value).Snapshot("the value");
    
    // multi-line content
    await Verify(value).Snapshot(
        """
        line one
        line two
        """);
  7. Using DiffEngineViewer with DiffEngineTray

    main

    When DiffEngineTray is running, it acts as the primary owner of the snapshot queue. The viewer interacts with the tray via a socket.

    Key Behaviors:

    • Persistence: The tray holds the queue, meaning snapshots persist even if the viewer is closed, killed, or crashes. This prevents a large process from needing to stay resident just to hold a list.
    • Attachment: Use the --attach flag to connect a viewer to a running tray. The tray will start a viewer automatically whenever a new snapshot arrives if no window is currently open.
    • Ownership: If a viewer is already running when the tray starts, the viewer retains ownership of the queue and the tray drives it remotely. Ownership does not move between the two.
    • Queue Management: The tray's Pending Snapshots group allows you to accept, discard, open a specific snapshot in the viewer, or close the viewer.
    • Data Safety: Exiting the queue owner (closing an owning viewer without a tray, or exiting the tray) writes pending inline snapshots to the source project's obj/VerifyInline/ directory. Note that a crash or a kill command will skip this step and result in the loss of pending file moves and deletes.
  8. Understand initial and detected difference behavior

    main

    DiffEngine manages how external diff tools are launched based on whether a file is being verified for the first time or if a difference is detected during an ongoing verification.

    Initial Verification

    When an input is verified for the first time, behavior depends on whether an EmptyFiles match for the current file extension exists:

    • If an EmptyFiles match is found: The tool launches to compare the input against that empty file.
    • If no EmptyFiles match is found: No tool is launched.

    Detected Differences

    When a difference is detected between the input and an existing verified file, the behavior depends on whether a tool is already running:

    If no tool is running:

    A new tool instance will be launched.

    If a tool is already running:

    The behavior depends on your Auto Refresh and Mdi settings:

    Auto RefreshMdiBehavior
    truetrueNo action. Current instance will refresh
    truefalseNo action. Current instance will refresh
    falsetrueOpen new instance. Previous instance must be manually closed
    falsefalseKill current and open new instance

    Note: The bundled DiffEngineViewer is an exception; it queues failing pairs into a single window and does not follow the logic above.

  9. How conflicting snapshots work

    main

    A conflict occurs when a test run under multiple target frameworks (e.g., net8.0 and net9.0) produces different content for the same call site.

    • Identification: Conflicted entries are marked with a * in the pending list. The pane header will indicate the active framework (e.g., received (net8.0)).
    • Resolution: Use the v key or the Variant button to cycle through the different framework contents.
    • Accepting: Accepting a variant applies only that specific variant to the source and resolves that call site.
    • Accept All: The Accept all command (and Shift+A) will skip conflicted entries to avoid picking sides incorrectly. It will report how many conflicted items still require manual review.
  10. Understand Diff Tool launch behavior

    main

    DiffEngine's behavior when launching tools depends on whether the input is being verified for the first time or if a difference has been detected during an ongoing verification.

    Initial Verification

    When an input is verified for the first time, DiffEngine looks for an EmptyFiles matching the file extension.

    • If found: The tool launches to compare the input against that empty file.
    • If not found: No tool is launched.

    Detected Differences

    If a difference is detected between the input and an existing verified file, behavior depends on whether a tool is already running for that specific test:

    If no tool is currently running:

    A new tool instance will be launched.

    If a tool is already running:

    The behavior depends on your Auto Refresh and Mdi (Multiple Document Interface) settings:

    Auto RefreshMdiBehavior
    truetrueNo action. Current instance will refresh
    truefalseNo action. Current instance will refresh
    falsetrueOpen new instance. Previous instance must be manually closed
    falsefalseKill current and open new instance

    Note: The bundled DiffEngineViewer is an exception; it queues every failing pair into a single window regardless of these settings.

  11. How DiffEngine renders new snapshot literals

    main

    When a patch is accepted, DiffEngine rewrites the source code. The rendering logic follows these rules:

    • Form:
      • Single-line content becomes a regular literal (with standard escaping).
      • Multi-line content becomes a raw string literal (""").
      • Empty content becomes "".
    • Delimiter: Uses three quotes, or one more than the longest run of quotes in the content (e.g., """" if the content contains """).
    • Placement:
      • Raw literals are placed on the line below the opening parenthesis, with the delimiter aligned with the content.
      • Regular literals stay within the argument list.
    • Indentation: The call line's leading whitespace plus one level. The indentation width is detected from the file (e.g., spaces or tabs).
    • Line Endings: Uses the file's dominant line ending. The rest of the file (encoding, BOM, etc.) is preserved byte-for-byte.