Skip to content

Testing

Loky edited this page Oct 14, 2026 · 7 revisions

Testing

Last run (v0.17.2): 31 suites, 250 tests, 0 failures (Godot 4.7.2, Windows).

Two layers: automated tests for the pure logic (GdUnit4: no real git process, no scene) and a manual regression checklist for everything that needs the editor or a real remote.

How the code is made testable

  • Parsers and helpers are static or RefCounted: text in, data out (GitStatusParser, GitDiffParser, GitRemote.web_url(), GitotUi.sync_symbol()...).
  • Panels take their widgets by constructor injection, and their text rules are static helpers (GitotBranchPanel, GitotLfsPanel, GithubPanel._header_text()), so the wording is tested without building a scene.
  • Result handlers (GitotBranchResults, GitotSyncResults, GitotResultRouter) are built with null panels and a null engine; a test only drives the paths that never reach them, so a missing guard crashes the test instead of passing silently.
  • A test that needs a collaborator gets a fake or null: GitLfs runs on a real GitEngine that never executes a command (the test emits command_completed itself); GitotPushFlow gets null for the engine, orchestrator and tag panel, because the code under test never reaches them.
  • Nothing touches your global git config, your git lfs install, the network or a real repository. The only disk I/O is GitIgnore, in a scratch folder under user://, never in the project.

Automated: what each suite covers

Parsers (git output to data)

Suite Tests Covers
git_status_parser_test 12 status --porcelain=v2: untracked, staged / unstaged / both, deleted, conflict, spaces, quoted non-ASCII paths, ignored and malformed lines, status args skip optional locks
git_diff_parser_test 14 Gutter hunks (added, modified, pure deletion), full diff viewer (hunks, files, malformed headers, a content line that looks like a file header), --name-status
git_log_parser_test 4 Entries, order, malformed lines, blank input
git_branch_parser_test 8 Current / other / remote branches, the origin/HEAD alias skipped, separator inside a subject, order
git_stash_parser_test 8 Custom and default (WIP) subjects, index from the selector, slashes and colons in names, a malformed line does not shift indices
git_lfs_parser_test 20 git lfs version parsing (version, other output), ls-files --json (local, pointer-only, defaults, non-ASCII), .gitattributes patterns (CRLF, empty), local / remote merge by oid, summary counts and sizes
git_path_test 11 Git's quoted paths to real UTF-8: octal bytes, tab, quote, backslash, empty, name-list results (reliable flag)

Engine, runner, planning

Suite Tests Covers
git_engine_test 13 Which commands take the write lock (push and pull yes, fetch and tag push no), init / remote add, per-call on_done (callback plus broadcast, also for a refused remote delete), latest-wins reads (a superseded read is not broadcast, its on_done still runs)
git_remote_test 8 owner/repo parsing, web_url() (accepted forms, refused unsafe forms), remote-URL decoding, remote URL validation
git_ref_test 4 Branch-name safety (shell metacharacters, leading -), tag-name rules
git_runner_test 21 Log parsing (only the final exit marker counts), Windows output re-decoding, shell string and exit marker, shell-safety whitelist, quoted-path check and combined refusal, a dropped write still completes (-1), the network write lock, long path lists split in chunks and chunk outputs merged
git_sync_orchestrator_test 4 plan_push(): normal, behind (blocked, never forced), diverged (force confirmation)
git_lfs_test 9 LFS state machine (no binary, not initialized, ready), transitions, pattern guards (empty, option-like, Godot text formats, catch-all like *), teardown, merge with the upstream list, a failed local read reports failure
gitot_regex_test 3 One compiled RegEx per pattern (same instance on reuse, distinct per pattern, matches)
git_ignore_test 4 Default .gitignore written only when missing, never overwrites, unwritable folder, template content

State, settings, messages

Suite Tests Covers
gitot_repo_state_test 6 Sync state, changed signal only on a real change, no-origin default, label fallbacks
gitot_settings_test 6 Every numeric setting clamped to its range (timeout minimum, 0 MB valid), pass-through values, ranges and defaults consistent, confirmations on by default, Output echo on by default
gitot_logger_test 4 The one failure shape: error, git's raw message, hint; [ escaped for every level; every level has a style
gitot_result_router_test 12 Commit summary (file list dropped, hook output skipped), stash and pop lines, a failed status or log repaints nothing, origin state, remote add success and failure, stage / unstage failure and success lines
gitot_branch_results_test 9 Every branch command dispatched, silent failed branch list, switch / create / delete failure text, current-branch answer (unborn branch / detached)
gitot_sync_results_test 14 Every sync command dispatched, push / pull / force-push lines, label fallbacks, failure shape, timeout hint, a pull moves HEAD and a push does not, ahead/behind state (the startup read is silent, later changes are logged), tag push
gitot_commit_flow_test 5 Pushed = upstream and nothing ahead, amend confirmation rule (pushed always asks), amend arguments (keep or replace the message), empty message commits nothing, amend toggle wording
gitot_push_flow_test 2 No origin asks for a remote; with an origin it does not
gitot_dock_test 2 Focus-in refresh cooldown (3 s), first focus-in is never skipped

UI text and rules (no scene)

Suite Tests Covers
gitot_ui_test 14 Sync key / symbol / color / counts / tooltips, ellipsize, scope label, push tooltip, amend confirmation texts
gitot_diff_panel_test 5 Raw diff to hunks (first file, binary / empty), truncation logged only over the cap, line-number width grows past 999
gitot_diff_gutter_test 4 Gutter added once per editor, teardown() hides it and clears its markers, a re-enabled plugin reuses it, a freed editor is skipped
gitot_branch_panel_test 12 Fold title, row annotation (local / remote copy), status text with counts, sync tooltips
gitot_lfs_panel_test 6 State text, setup steps, dashboard stats (empty, zero counters, bracket escaping)
github_panel_test 5 Issues header: repo, open / Bug / High priority counts, no count, before any fetch; github.com-only remote detection; dirty count from the parsed status
github_issue_parser_test 1 count_where(): exact match only, Urgent is not High, empty list

Conventions

  • One file per class: test/<class_snake_case>_test.gd, extends GdUnitTestSuite, a header comment saying what the suite covers.
  • One behavior per test, named for it (test_behind_only_is_blocked_never_forced), not for the function.
  • A bug fix comes with the test that would have caught it (the failed branch list that crashed outside a repository, the unborn branch shown as detached, the failed status shown as a clean tree).
  • GdUnit runs tests in a separate game process, where EditorInterface does not exist: a class whose constructor reads the editor theme (GitotStatusTree) cannot be built in a test. Keep the logic in static helpers or plain objects.
  • Security rules are tested from both sides, accepted and refused inputs: remote URLs, branch and tag names, shell arguments, web_url() (no token in a link).
  • No test may start a real git process. In git_lfs_test, never emit LFS_VERSION with exit 0 or LFS_INSTALL: both make GitLfs run one.

Manual

The editor flows are checked by hand with a regression-checklist before each release: lifecycle, stage, commit / sync, no repository / first push (Initialize repository, remote URL prompt), status line link, stash, branches, tags, history, gutter, GitHub Issues (including the header counts), output messages, settings.

Not covered

  • Git LFS against a real LFS remote (Install LFS, real push / pull / prune, the not-installed views).
  • Git older than 2.32, and a project inside a repository subfolder.
  • Linux, macOS, and code pages other than Windows-1252.

Clone this wiki locally