AI refac
Development

بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ

Testing & Debugging

How to run the test suite, how the tests are grouped, and how to debug a failure.

This page will inshallah help you run the right tests and find the cause when one fails.

Run the tests

cargo test
bun install --cwd scripts --frozen-lockfile
bun run --cwd scripts test
bun run --cwd scripts typecheck

A plain cargo test needs only the Rust toolchain, plus Bun for the TypeScript engine. The CI definition runs cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test, so those three commands are also the check before a push.

Run a targeted test:

cargo test drivers::rust::tests::test_rust_cross_dir_move_keeps_project_buildable -- --nocapture

Six test groups

Every file directly in tests/ would be its own program that links the whole library, about 90 MB each. So tests/ holds groups, one program per folder (tests/<group>/main.rs, with the files of the group as modules). Add a module to a group, and do not add a .rs file directly in tests/.

Group (--test)HoldsReal server needed
cliUsage errors, --help, refac guide, doctor, rename --batch refusals, the answer and exit code of a partly failed move, and the layout testNo
movesmove per language, move-module, multi-language batchesPartly. The Go, Dart, and Python tests are #[ignore]d
renameSymbol rename in Go, Rust, Python, and Dart, encoding, batchYes, #[ignore]d
typescriptMoves, rename, limits, and the 3,005-file stress testNo. refac installs the TypeScript engine
markdownLink rewriting, sites, a corpus of link shapes, scale, safetyNo
kotlinServer, moves, rename, dispatch, Android, MultiplatformYes, #[ignore]d

Run one group with cargo test --test <group>, a part of it with a module filter (cargo test --test rename go::), and the real-server tests with -- --ignored.

Real-server tests

The scenarios that start a real language server are #[ignore]d, so a plain cargo test shows them as ignored and does not skip them silently. With the tool installed, run them with -- --ignored, or -- --include-ignored for everything. Without the tool they fail with refac's own missing-server explanation, which names the places looked at, the environment variable, and refac doctor <language>. Each successful scenario ends by building or running the changed project, because a refactor is right when the project still works.

cargo test --test rename -- --ignored

They need gopls, rust-analyzer, basedpyright, and the Dart SDK on PATH or through REFAC_GOPLS, REFAC_RUST_ANALYZER, REFAC_PYTHON_SERVER, and REFAC_DART.

Kotlin tests

Do not call Kotlin tests like crazy. Each one starts a language server and a Gradle import (20 to 47 seconds, about 2 GB), and the whole group takes about 7 minutes. By default only the quick set runs, within 30 seconds:

export REFAC_KOTLIN_SERVER=~/.local/share/refac/kotlin-server-<build>
cargo test --test kotlin quick:: -- --ignored --test-threads=1

Every other Kotlin test is blocked and fails at once with the reason, unless the command says REFAC_KOTLIN_TESTS=all. Put the tests you want into one command with --test-threads=1. They share one server per fixture inside it, so the command pays the start once per fixture. Android tests also need ANDROID_HOME.

The shape of the code is a test

tests/cli/layout.rs fails the run when the code drifts from the rules:

  • No .rs file directly in tests/.
  • Every code file is declared by its parent module, because a file nobody declares is never compiled and its tests never run.
  • At most 300 lines of code per file. Blank lines and comments do not count.
  • At most 9 code files directly in one folder, so related files get a folder.

Fixtures

Each language has a fixture directory under tests/fixtures/ and a test file in a group:

LanguageFixtureTest fileUnder test
TypeScripttypescript/project/tests/typescript/moves.rssrc/models/User.ts to src/core/User.ts
Pythonpython/project/tests/moves/python.rsmyapp/utils/formatters.py to myapp/core/formatters.py
Rustrust/project/tests/moves/rust.rs, tests/moves/rust_module/The refusal of a cross-folder .rs move, a rejected #[path] module, the rollback after a failed Cargo check, and the move-module scenarios
Gogo/project/tests/moves/go.rspkg/utils/format.go to pkg/helpers/format.go
Dartdart/project/tests/moves/dart.rslib/src/formatter.dart to lib/src/core/formatter.dart, and the refusal without package_config.json
Markdownmarkdown/tests/markdown/Link rewrites, folders, scale, safety
Kotlinkotlin/jvm_project/, android_project/, kmp_project/tests/kotlin/Package moves, rename, rollback, Android XML, Multiplatform

Fixtures are never modified. Each test copies the fixture into a temp directory first. The tool works on the copy, the original stays pristine, and the temp directory is cleaned up when the test ends.

Batch tests

tests/moves/batch.rs exercises multi-file invocations with the real CLI binary: unrelated and cross-importing TypeScript pairs, sequential Rope moves, one rust-analyzer session for two renames, same-package and cross-package Go batches, Dart and Markdown pairs, a mixed TypeScript and Markdown call, a partial failure that reports both outcomes, and a total failure that exits non-zero with a structured error.

Implementation notes

Go temp directories. The test helper uses refac-test- as the prefix of temp directories and not a hidden .tmp prefix. gopls skips workspace roots whose directory name starts with ., so hidden temp directories stop the import cascade in Go tests.

Dart serialization. The Dart analysis server is sensitive to concurrent starts. The Dart move tests take a global lock before each test, so at most one analysis server runs at a time.

No fixed waits. The Dart, Go, Rust, and Pyrefly drivers wait for the server's own readiness signal, so the suites pass on a busy machine too. A server that never becomes ready fails after REFAC_LSP_TIMEOUT_SECS (default 300). Set it lower when you debug a hang.

Manual CLI verification

Generate sample projects and run the CLI against one:

cargo run --bin create_testbed

./target/debug/refac move \
  --project-path Trials/0_Refac_Tree/typescript \
  --source-path src/models/TaskManager.ts \
  --target-path src/core/TaskManager.ts

Post-move validation

After a move, verify with the native toolchain:

LanguageCommand
Rustcargo check
Gogo build ./...
TypeScriptThe project's type-check or build
PythonImport the affected modules or run the project's tests
Dartdart analyze or the project's build
Kotlin./gradlew compileKotlin compileJava, or the project's usual build

All of them should inshallah pass. Read the // Note: lines of the output for the places refac does not edit.

Common debugging

TypeScript is slow or hangs. --project-path is too broad. Point it at the specific package with its tsconfig.json, not at a monorepo root. The helper is stopped after 5 minutes or 4 GiB, so inspect the working tree after such a stop.

"Source path does not exist". --source-path is resolved relative to --project-path. If the file exists and refac cannot find it, the project path is wrong.

A language server is missing. The error lists the places that were looked at. Run refac doctor <language> for the install steps, install the tool, and run the command again.

A server misbehaves. REFAC_LSP_TRACE=1 prints every message exchanged with the server, and RUST_LOG=debug shows more of refac's own log.

On this page